İçeriğe geç
Educora
İleri22 dk20 / 42

Düzenli ifadeler: re modülü

Metinde kalıba göre arama: `re.search`, `findall`, `finditer`, karakter sınıfları ve niceleyiciler, gruplar ve adlandırılmış gruplar, açgözlü ve tembel eşleşme, `sub`, `split`, bayraklar ve `compile`.

Kendini test et
Bu derste öğreneceklerin
  • Karakter sınıfları, niceleyiciler ve çapalarla kalıp yazmak
  • Gruplar ve adlandırılmış gruplarla metinden parçalar çıkarmak
  • Açgözlü ve tembel niceleyicileri ayırt etmek
  • re.sub, re.split, bayraklar ve re.compile ile metin işlemek

Uzun bir metindeki tüm telefon numaralarını bul, bir e-posta adresinin geçerli görünüp görünmediğini denetle, bir milyon satırlık günlük dosyasından tarihleri ve hata düzeylerini çıkar. find() ve split() ile her görev bir sayfalık kırılgan kod ister. Düzenli ifade (regex), aradığın metnin biçimini kısa bir kalıpla tanımlar; aramayı ise re modülü yapar. Düzenli ifadeler her yerde kullanılır: metin düzenleyicilerde, veritabanlarında, komut satırı araçlarında ve web sitelerindeki form doğrulamalarında.

İlk adımlar: search, findall ve ham dizeler

Kalıp, küçük ve özel bir dille yazılmış bir karakter dizisidir. re.search(pattern, text) metnin herhangi bir yerindeki ilk eşleşmeyi bulur ve bir Match nesnesi (ya da None) döndürür; .group() eşleşen metni verir. re.findall ise tüm eşleşmelerin listesini döndürür. \d “bir rakam”, + ise “bir ya da daha fazla” demektir:

Python
import re

text = 'Order 17 shipped on 2026-09-15, order 245 on 2026-09-20.'
print(re.search(r'\d+', text))
print(re.search(r'\d+', text).group())
print(re.findall(r'\d+', text))
print(re.findall(r'\d{4}-\d{2}-\d{2}', text))
print(re.search(r'cancelled', text))
▸ Beklenen çıktı
<re.Match object; span=(6, 8), match='17'>
17
['17', '2026', '09', '15', '245', '2026', '09', '20']
['2026-09-15', '2026-09-20']
None

Kalıpların dili

SimgeAnlamıÖrnek
.yeni satır dışında herhangi bir karaktera.c → abc, a-c
\d, \w, \srakam, sözcük karakteri (harf, rakam, _), boşluk karakteri\d\d → 42
\D, \W, \Stersi: rakam değil, sözcük karakteri değil, boşluk değil\S+ → 2026-09-15
[abc], [a-z], [^0-9]kümeden / aralıktan bir karakter, kümede olmayan bir karakter[aeiou] → e
*, +, ?0 ya da daha fazla, 1 ya da daha fazla, 0 ya da 1colou?r → color, colour
{n}, {n,m}tam n kez, n ile m arası tekrar\d{4} → 2026
^, $, \bdizenin başı ve sonu, sözcük sınırı^\d+$
a|b, (...)seçenek, grup(cat|dog)s
\., \(, \+özel karakterin kendisi: nokta, parantez, artı\+994
Python
import re

print(re.findall(r'\w+', 'Salam, dünya! Привет, мир!'))
print(re.findall(r'\b\w{4}\b', 'Baku is a large city by the sea'))
print(re.findall(r'colou?r', 'color or colour?'))
print(re.findall(r'[A-Z]\w*', 'Aysel and Murad met Leyla'))
▸ Beklenen çıktı
['Salam', 'dünya', 'Привет', 'мир']
['Baku', 'city']
['color', 'colour']
['Aysel', 'Murad', 'Leyla']

Python 3'te \w Unicode'u anlar: her alfabenin harfleriyle eşleşir; bu yüzden Azerbaycanca, Türkçe ve Kiril alfabesiyle yazılmış sözcükler de bulunur. \b, bir sözcük karakteri ile sözcük olmayan bir karakter arasındaki görünmez sınırla eşleşir; bu nedenle \b\w{4}\b daha uzun sözcüklerin parçalarını değil, dört harfli tam sözcükleri bulur.

Gruplar ve adlandırılmış gruplar

Parantezler bir grup oluşturur: eşleşmenin gerçekten ihtiyaç duyduğun kısmı. m.group(n) n numaralı grubu döndürür (0 numaralı grup tüm eşleşmedir), m.groups() ise hepsini bir demet olarak verir. Numaralı gruplar çabucak kafa karıştırır; bu yüzden onlara (?P<name>...) ile ad ver ve m['name'] şeklinde ya da hepsini birden m.groupdict() ile oku. re.match yalnızca dizenin başında dener; tek bir günlük satırı için tam uygundur:

Python
import re

line = '2026-09-15 14:02:11 ERROR [payment] card declined'
m = re.match(r'(\S+) (\S+) (\w+) \[(\w+)\] (.*)', line)
print(m.group(3), '|', m.group(5))
print(m.groups())

named = r'(?P<date>\S+) (?P<time>\S+) (?P<level>\w+) \[(?P<module>\w+)\] (?P<msg>.*)'
m = re.match(named, line)
print(m['level'], m['module'])
print(m.groupdict())
▸ Beklenen çıktı
ERROR | card declined
('2026-09-15', '14:02:11', 'ERROR', 'payment', 'card declined')
ERROR payment
{'date': '2026-09-15', 'time': '14:02:11', 'level': 'ERROR', 'module': 'payment', 'msg': 'card declined'}

Kalıpta gruplar varsa findall tüm eşleşmeyi değil grupları döndürür; birden fazla grup varsa demetler hâlinde. Konumlara ya da adlandırılmış gruplara da ihtiyacın varsa, Match nesnelerini tembelce üreten **finditer** ile döngü kur:

Python
import re

phones = 'Aysel: +994 50 123 45 67, Murad: +994 55 765 43 21'
print(re.findall(r'\+994 (\d{2}) (\d{3} \d{2} \d{2})', phones))
for m in re.finditer(r'(?P<name>\w+): (?P<phone>\+994[\d ]+\d)', phones):
    print(m['name'], '->', m['phone'], m.span())
▸ Beklenen çıktı
[('50', '123 45 67'), ('55', '765 43 21')]
Aysel -> +994 50 123 45 67 (0, 24)
Murad -> +994 55 765 43 21 (26, 50)

Açgözlü ve tembel niceleyiciler

Niceleyiciler varsayılan olarak açgözlüdür (greedy): .+ alabildiği kadar çok metin alır ve karakterleri ancak kalıbın geri kalanı eşleşmeyecekse geri verir. Sonuna ? eklersen niceleyici tembel olur (.+?, *?, ??): olabildiğince az alır. \1 bir geri başvurudur: 1. grupla aynı metinle eşleşmelidir; bu yüzden kapanış etiketi açılış etiketine uyar:

Python
import re

html = '<b>bold</b> and <i>italic</i>'
print(re.findall(r'<.+>', html))
print(re.findall(r'<.+?>', html))
print(re.findall(r'<(\w+)>(.*?)</\1>', html))
▸ Beklenen çıktı
['<b>bold</b> and <i>italic</i>']
['<b>', '</b>', '<i>', '</i>']
[('b', 'bold'), ('i', 'italic')]

Değiştirme, bölme, bayraklar ve compile

re.sub(pattern, replacement, text) tüm eşleşmeleri değiştirir. Yerine konacak dizede \1, \2… grupları ekler; dize yerine, Match nesnesini alıp yeni metni döndüren bir fonksiyon da verebilirsin. re.split metni bir kalıba göre böler; örneğin virgül, noktalı virgül ve boşlukların herhangi bir karışımına göre:

Python
import re

text = 'Call 050-123-45-67 or 055-765-43-21 today'
print(re.sub(r'\d{3}-\d{3}-\d{2}-\d{2}', '[hidden]', text))
print(re.sub(r'(\d{3})-(\d{3})-(\d{2})-(\d{2})', r'(\1) \2 \3 \4', text))
print(re.sub(r'\d+', lambda m: str(int(m.group()) * 2), 'a1 b22 c333'))
print(re.split(r'[,;\s]+', 'tea, coffee;  juice water'))
▸ Beklenen çıktı
Call [hidden] or [hidden] today
Call (050) 123 45 67 or (055) 765 43 21 today
a2 b44 c666
['tea', 'coffee', 'juice', 'water']

Bayraklar bir kalıbın çalışmasını değiştirir: re.IGNORECASE (re.I) büyük-küçük harf ayrımını yok sayar, re.MULTILINE (re.M) ^ ve $'ın her satırın başı ve sonuyla eşleşmesini sağlar, re.DOTALL (re.S) .'nın yeni satırla da eşleşmesine izin verir, re.VERBOSE (re.X) ise kalıp içinde boşluklara ve # yorumlarına olanak tanır. Birçok kez kullanılan bir kalıp re.compile ile bir kez derlenebilir:

Python
import re

EMAIL = re.compile(r'''
    [\w.+-]+           # user name
    @
    [\w-]+(\.[\w-]+)+  # domain with at least one dot
''', re.VERBOSE)

for address in ['leyla@mail.az', 'elvin.m@edu.gov.az', 'wrong@site', 'no at.com']:
    print(f'{address:20} {bool(EMAIL.fullmatch(address))}')

log = 'ERROR one\ninfo two\nError three'
print(re.findall(r'^error.*$', log, re.IGNORECASE | re.MULTILINE))
▸ Beklenen çıktı
leyla@mail.az        True
elvin.m@edu.gov.az   True
wrong@site           False
no at.com            False
['ERROR one', 'Error three']
  • re.match — yalnızca dizenin başında dener.
  • re.search — herhangi bir yerdeki ilk eşleşmeyi bulur.
  • re.fullmatch — dizenin tamamı eşleşmelidir.
  • re.findall / re.finditer — tüm eşleşmeler (liste / Match nesnelerinin yineleyicisi).
  • re.sub, re.split — değiştirme ve bölme.
Örnek: cep telefonu biçimini denetlemek

Bir kayıt formunda telefon tam olarak +994 50 123 45 67 biçiminde yazılmalıdır: ülke kodu, iki basamaklı operatör kodu ve tek boşluklarla ayrılmış 3 + 2 + 2 rakam. +994 501234567 ve +994 50 123 45 67 ext reddedilmelidir. Kalıbı kur.

Çözümü göster
+ özel bir karakterdir; bu yüzden \+994 yazarız.
Operatör kodu \d{2}, diğer parçalar \d{3}, \d{2}, \d{2}'dir; aralarında tam bir boşluk vardır.
Kalıp: r'\+994 \d{2} \d{3} \d{2} \d{2}'.
re.fullmatch dizenin tamamının eşleşmesini ister; bu yüzden sondaki ext numarayı geçersiz kılar.
Alıştırma

Metindeki DD.MM.YYYY biçimindeki tüm tarihlerin listesini yazdır, ardından her tarihi YYYY-MM-DD biçimine çevirerek metni yeniden yazdır.

Alıştırma · Python
import re

text = 'Exams: 15.06.2026 (math), 18.06.2026 (physics), 22.06.2026 (history).'
# 1) print the list of all dates DD.MM.YYYY
# 2) print the text with every date rewritten as YYYY-MM-DD
▸ Beklenen çıktı
['15.06.2026', '18.06.2026', '22.06.2026']
Exams: 2026-06-15 (math), 2026-06-18 (physics), 2026-06-22 (history).
Alıştırma

key = value satırlarından (eşittir işaretinin çevresinde boşluk olabilir de olmayabilir de) bir {anahtar: değer} sözlüğü oluştur ve yazdır. # ile başlayan yorum satırları atlanmalıdır.

Alıştırma · Python
import re

config = 'host = localhost\nport=8080\n# comment\ndebug =  true'
# build a dict {key: value} from the 'key = value' lines and print it
▸ Beklenen çıktı
{'host': 'localhost', 'port': '8080', 'debug': 'true'}

Önemli noktalar

  • Kalıpları ham dize r'...' olarak yaz; search ilk eşleşmeyi, findall hepsini bulur, fullmatch ise dizenin tamamını denetler.
  • Kalıplar \d \w \s, [...] sınıfları, * + ? {n,m} niceleyicileri ve ^ $ \b çapalarıyla kurulur.
  • Gruplar (...) ve adlandırılmış gruplar (?P<name>...) parçaları çıkarır: m.group(1), m['name'], m.groupdict().
  • Niceleyiciler açgözlüdür; tembel eşleşme için ? ekle (.+?).
  • re.sub, \1 geri başvurularını ve fonksiyonları kabul eder; re.I, re.M, re.S, re.X bayrakları eşleşmeyi değiştirir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Bir kalıbı neden r'\d+' gibi ham dize olarak yazarız?