Python'da düzenli ifadelerle (regex) çalışmak için standart re modülü kullanılır. Metnin herhangi bir yerinde ilk eşleşmeyi bulmak için re.search(), yalnızca başında aramak için re.prefixmatch() (eski adıyla re.match()), metnin tamamının desene uymasını denetlemek için re.fullmatch(), tüm eşleşmeleri liste olarak almak için re.findall(), değiştirmek için re.sub() ve bölmek için re.split() çağırırsınız. Desenleri her zaman r"..." biçiminde ham metin (raw string) olarak yazmak, ters eğik çizgi sorunlarını baştan önler.
Bu yazı düzenli ifade sözdiziminin kendisini değil, Python'daki re modülünün nasıl kullanıldığını anlatır: fonksiyonlar arasındaki farklar, Match nesnesi, gruplar, bayraklar, Türkçe karakterlerin \w ve IGNORECASE ile davranışı, Python 3.13, 3.14 ve 3.15'te gelen değişiklikler ve sık yapılan hatalar. Karakter sınıfları, niceleyiciler ve çapalar gibi genel regex kavramlarına yabancıysanız önce düzenli ifadeler başlangıç rehberini okuyabilirsiniz. Serinin geri kalanı için Python öğrenme rehberi sayfasına bakın.
Ham metin (raw string) neden şart?
Hem Python metinleri hem düzenli ifadeler ters eğik çizgiyi özel anlamda kullanır. Normal bir Python metninde "\n" tek bir yeni satır karakteridir; düzenli ifade motoruna ise iki karakterlik \n ulaşmalıdır. Belgeler bu çakışmayı açıkça anlatır: değişmez bir ters eğik çizgiyi eşleştirmek için normal metinde '\\\\' yazmanız gerekir. r öneki ile yazılan ham metinde ters eğik çizgiler hiç işlenmez, bu yüzden desenler neredeyse her zaman ham metin olarak yazılır.
Python'un tanımadığı kaçış dizileri artık sessizce geçiştirilmiyor. "\d+" gibi ham olmayan bir desen yazdığınızda Python 3.15.2 derleme sırasında SyntaxWarning: "\d" is an invalid escape sequence. Such sequences will not work in the future. Did you mean "\\d"? A raw string is also an option. uyarısını verir. Kod bugün hâlâ çalışır, ama belgeler bunun ileride SyntaxError olacağını söyler. Uyarıyı gördüğünüzde deseni r"\d+" olarak düzeltin.
import re
desen = r"\d+" # doğru: ham metin
print(re.findall(desen, "a1b22"))
# çıktı: ['1', '22']
print(len("\n"), len(r"\n"))
# çıktı: 1 2search, prefixmatch ve fullmatch farkı
Bu üç fonksiyon da ilk eşleşme için bir Match nesnesi, eşleşme yoksa None döndürür. Farkları desenin metnin neresinde aranacağıdır. search() metni baştan sona tarar ve desenin uyduğu ilk konumu bulur. prefixmatch() yalnızca metnin başında eşleşme arar; MULTILINE kipinde bile her satırın başına değil, yalnızca metnin başına bakar. fullmatch() ise metnin tamamının desene uymasını ister; posta kodu, telefon numarası ya da kullanıcı adı gibi bir girdinin biçimini doğrularken doğru araç budur.
import re
metin = "Sipariş 1042 hazırlandı, kargo kodu TR-88213."
m = re.search(r"\d+", metin)
print(m)
# çıktı: <re.Match object; span=(8, 12), match='1042'>
print(m.group(), m.start(), m.end(), m.span())
# çıktı: 1042 8 12 (8, 12)
print(re.prefixmatch(r"\d+", metin))
# çıktı: None
print(re.prefixmatch(r"Sipariş", metin))
# çıktı: <re.Match object; span=(0, 7), match='Sipariş'>
print(re.fullmatch(r"\d{5}", "34710"), re.fullmatch(r"\d{5}", "34710a"))
# çıktı: <re.Match object; span=(0, 5), match='34710'> None
if eslesme := re.search(r"TR-(\d+)", metin):
print(eslesme[1])
# çıktı: 88213Match nesneleri her zaman doğru (truthy) kabul edilir. Belgeler bu yüzden sonucu doğrudan if ile ve walrus operatörüyle (:=) denetlemeyi örnek gösterir. Eşleşme olmadığında None döndüğü için re.search(...).group() gibi zincirleme yazım, eşleşme yokken AttributeError: 'NoneType' object has no attribute 'group' hatasına yol açar. Sonucu kullanmadan önce mutlaka denetleyin.
Python 3.15: match() yerine prefixmatch()

Python 3.15'te re.prefixmatch() ve Pattern.prefixmatch() eklendi; eski re.match() ve Pattern.match() ise yumuşak kullanımdan kaldırıldı (soft deprecated). What's New belgesi gerekçeyi şöyle açıklar: diğer dillerdeki regex kütüphanelerinin çoğu “match” sözcüğünü Python'un search() dediği, metnin herhangi bir yerinde arama davranışı için kullanır. Python'daki match() ise yalnızca başta arar ve bu, başka dillerden gelen geliştiriciler için yıllardır bir tuzaktı. Yeni ad davranışı açıkça söyler.
Yumuşak kullanımdan kaldırma, kodun çalışmayı bırakacağı anlamına gelmez. Belgeler eski match() adını kaldırma planı olmadığını, uyarı da vermediğini belirtir; yalnızca yeni kodda yeni adın tercih edilmesi önerilir. Kodunuzun 3.14 ve öncesinde de çalışması gerekiyorsa match() kullanmaya devam edebilirsiniz; prefixmatch() eski sürümlerde yoktur. Bu yazının diğer örneklerinde yeni ad kullanılıyor. Sürümün tüm yenilikleri için Python 3.15 yenilikleri yazısına bakın.
| Fonksiyon | Nerede arar | Döndürdüğü | Tipik kullanım |
|---|---|---|---|
| search | metnin her yerinde, ilk eşleşme | Match veya None | metinde bir şey var mı |
| prefixmatch (match) | yalnızca metnin başında | Match veya None | metin bununla mı başlıyor |
| fullmatch | metnin tamamı | Match veya None | girdi doğrulama |
| findall | tüm metin | liste (metin veya tuple) | tüm değerleri toplamak |
| finditer | tüm metin | Match yineleyicisi | konum ve grup bilgisiyle tüm eşleşmeler |
| sub / subn | tüm metin | yeni metin / (metin, sayı) | değiştirme |
| split | tüm metin | liste | desene göre bölme |
findall, finditer ve gruplar

findall() çakışmayan tüm eşleşmeleri soldan sağa bir liste olarak döndürür. Sonucun biçimi desendeki yakalayan grup sayısına bağlıdır ve bu, en sık şaşırtan ayrıntılardan biridir. Grup yoksa listede tam eşleşmeler, tek grup varsa yalnızca o grubun içeriği, birden fazla grup varsa her eşleşme için grupların tuple'ı bulunur. (?:...) biçimindeki yakalamayan gruplar sonucu etkilemez; bir alternatifi gruplamanız gerekip içeriğini almak istemiyorsanız bunu kullanın. Konum ya da adlandırılmış grup bilgisine ihtiyacınız varsa finditer() her eşleşme için bir Match nesnesi üretir.
import re
metin = "Ali: 0532 111 2233, Ayşe: 0216 444 5566"
print(re.findall(r"\d{4} \d{3} \d{4}", metin))
# çıktı: ['0532 111 2233', '0216 444 5566']
print(re.findall(r"(\w+): (\d{4})", metin))
# çıktı: [('Ali', '0532'), ('Ayşe', '0216')]
print(re.findall(r"(?:\w+): (\d{4})", metin))
# çıktı: ['0532', '0216']
for m in re.finditer(r"(?P<ad>\w+): (?P<tel>[\d ]+\d)", metin):
print(m["ad"], m.group("tel"), m.span())
# çıktı: Ali 0532 111 2233 (0, 18)
# çıktı: Ayşe 0216 444 5566 (20, 39)
m = re.search(r"(?P<gun>\d{2})\.(?P<ay>\d{2})\.(?P<yil>\d{4})", "Teslim: 07.11.2026")
print(m.groupdict(), m.groups())
# çıktı: {'gun': '07', 'ay': '11', 'yil': '2026'} ('07', '11', '2026')Grup numaraları 1'den başlar; group(0) ya da m[0] eşleşmenin tamamını verir. (?P<ad>...) biçimindeki adlandırılmış gruplar hem adla hem numarayla okunabilir ve desen büyüdükçe kodu çok daha anlaşılır kılar. groups() tüm grupları tuple olarak, groupdict() adlandırılmış grupları sözlük olarak verir. Eşleşmeye katılmayan bir grubun değeri None olur; groups(default='0') ile başka bir varsayılan verebilirsiniz. Belgelerin bir ayrıntısı daha: bir grup birden fazla kez eşleşirse yalnızca son eşleşmesi saklanır.
sub ve split ile değiştirme ve bölme
re.sub(desen, yerine, metin) eşleşen tüm parçaları değiştirir; count ile en fazla kaç değişiklik yapılacağını sınırlayabilirsiniz. Değiştirme metninde \1 gibi numaralı ya da \g<ad> gibi adlandırılmış geri başvurular kullanılabilir. \g<2>0 yazımı, \20 yazımının yirminci grup olarak okunması belirsizliğini ortadan kaldırır. Değiştirme metni yerine bir fonksiyon da verebilirsiniz: fonksiyon her eşleşmenin Match nesnesini alır ve yerine geçecek metni döndürür. Bu, hesaplama gerektiren değiştirmeler için idealdir. subn() aynı işi yapar ama (yeni metin, değişiklik sayısı) tuple'ı döndürür.
import re
print(re.sub(r"\s+", " ", "Çok fazla boşluk\tvar"))
# çıktı: Çok fazla boşluk var
print(re.sub(r"(\d{2})\.(\d{2})\.(\d{4})", r"\3-\2-\1", "07.11.2026 ve 01.01.2027"))
# çıktı: 2026-11-07 ve 2027-01-01
print(re.sub(r"(?P<ad>\w+)@", r"\g<ad> [at] ", "ayse@ornek.com"))
# çıktı: ayse [at] ornek.com
def kdv_ekle(m):
tutar = float(m.group(1))
return f"{tutar * 1.2:.2f} TL"
print(re.sub(r"(\d+(?:\.\d+)?) TL", kdv_ekle, "Kalem 10 TL, defter 25.5 TL"))
# çıktı: Kalem 12.00 TL, defter 30.60 TL
print(re.subn(r"a", "e", "kalabalık"))
# çıktı: ('kelebelık', 3)
print(re.split(r"[;,]\s*", "elma, armut;muz , kiraz"))
# çıktı: ['elma', 'armut', 'muz ', 'kiraz']
print(re.split(r"(-)", "2026-11-07"))
# çıktı: ['2026', '-', '11', '-', '07']split() desende yakalayan grup varsa ayırıcıları da sonuca ekler; yukarıdaki son örnekte tireler listede kaldı. Ayırıcıyı istemiyorsanız grup kullanmayın ya da (?:...) yazın. Ayırıcı listesinde dikkat edilecek bir nokta da “muz ” öğesindeki boşluktur: desen ayırıcıdan sonraki boşlukları yuttu ama öncesindekileri değil. Basit, sabit bir ayırıcıyla bölüyorsanız str.split() daha hızlı ve okunaklıdır; metin metotları için string metotları yazısına bakabilirsiniz.
Açgözlü ve tembel niceleyiciler
*, + ve ? niceleyicileri varsayılan olarak açgözlüdür (greedy): olabildiğince çok karakter yakalarlar. HTML etiketleri gibi sınırlayıcılar arasındaki parçaları ararken bu, ilk açılıştan son kapanışa kadar her şeyin tek eşleşme olmasına yol açar. Niceleyicinin arkasına ? eklemek onu tembel (non-greedy) yapar ve olabildiğince az karakter yakalar. Belgelerdeki örnek de tam olarak budur.
import re
print(re.findall(r"<.*>", "<b>kalın</b> ve <i>eğik</i>"))
# çıktı: ['<b>kalın</b> ve <i>eğik</i>']
print(re.findall(r"<.*?>", "<b>kalın</b> ve <i>eğik</i>"))
# çıktı: ['<b>', '</b>', '<i>', '</i>']Bayraklar ve Türkçe karakterler

Bayraklar desenin davranışını değiştirir ve | ile birleştirilebilir: re.I | re.M. Her birinin desen içinde kullanılabilen satır içi karşılığı da vardır, örneğin (?i). Python 3'te str desenleri varsayılan olarak Unicode'dur: \w tüm Unicode harf ve rakamlarını, \d Unicode ondalık rakamlarını, \s Unicode boşluk karakterlerini kapsar. Türkçe metinler için bu iyi haberdir: ş, ğ, ı ve İ harfleri \w ile eşleşir. re.ASCII bayrağı verildiğinde ise bu sınıflar yalnızca ASCII karakterlere daralır ve Türkçe kelimeler parçalanır.
import re
print(re.findall(r"\w+", "Şişli'de İğneada çayı"))
# çıktı: ['Şişli', 'de', 'İğneada', 'çayı']
print(re.findall(r"\w+", "Şişli'de İğneada çayı", flags=re.ASCII))
# çıktı: ['i', 'li', 'de', 'neada', 'ay']
print(re.findall(r"\d+", "Fiyat: ٣٤ ve 34"))
# çıktı: ['٣٤', '34']
print(re.findall(r"[0-9]+", "Fiyat: ٣٤ ve 34"))
# çıktı: ['34']
print(re.findall(r"istanbul", "İSTANBUL istanbul Istanbul", flags=re.IGNORECASE))
# çıktı: ['İSTANBUL', 'istanbul', 'Istanbul']
print(re.findall(r"^\w+", "birinci satır\nikinci satır", flags=re.MULTILINE))
# çıktı: ['birinci', 'ikinci']
print(re.findall(r"a.b", "a\nb", flags=re.DOTALL), re.findall(r"a.b", "a\nb"))
# çıktı: ['a\nb'] []Üçüncü ve dördüncü satırlar önemli bir güvenlik ayrıntısını gösterir: \d yalnızca 0-9 değil, Arapça-Hint rakamları gibi başka yazı sistemlerindeki rakamlarla da eşleşir. Girdinin yalnızca ASCII rakam içermesini istiyorsanız [0-9] yazın ya da re.ASCII kullanın. IGNORECASE satırında ise belgelerde özel olarak anılan bir durum görülüyor: büyük/küçük harf duyarsız eşleşmede noktalı büyük İ (U+0130) ve noktasız küçük ı (U+0131) de Latin i harfiyle eşleşebilir. Bu sayede “istanbul” deseni “İSTANBUL” metnini buldu. Türkçe büyük-küçük harf dönüşümlerinin ayrıntıları için Unicode ve Türkçe karakter yazısına bakın.
| Bayrak | Satır içi | Etkisi |
|---|---|---|
| re.IGNORECASE (re.I) | (?i) | Büyük/küçük harf duyarsız eşleşme |
| re.MULTILINE (re.M) | (?m) | ^ ve $ her satırın başında ve sonunda eşleşir |
| re.DOTALL (re.S) | (?s) | Nokta yeni satırla da eşleşir |
| re.VERBOSE (re.X) | (?x) | Desende boşluk ve # yorumlarına izin verir |
| re.ASCII (re.A) | (?a) | \w, \d, \s, \b yalnızca ASCII ile çalışır |
| re.NOFLAG | yok | Bayrak yok (değeri 0), varsayılan parametre için |
re.compile, VERBOSE ve escape
re.compile() deseni bir Pattern nesnesine derler; bu nesnenin search(), fullmatch(), sub() gibi metotları modül fonksiyonlarıyla aynı işi yapar ve ayrıca pos ve endpos ile aramayı metnin bir bölümüyle sınırlamanıza izin verir. Belgeler, aynı desen bir programda birçok kez kullanılacaksa derlenmiş nesneyi saklamanın daha verimli olduğunu söyler; ama modül son kullanılan desenleri zaten önbelleğe aldığı için, az sayıda desen kullanan programların derleme konusunda endişelenmesine gerek olmadığını da ekler. Pratikte compile() çoğu zaman okunabilirlik içindir: deseni bir kez, anlamlı bir adla tanımlarsınız.
re.VERBOSE bayrağı uzun desenleri satırlara bölmenize ve yorum eklemenize izin verir. Bu kipte desendeki boşluklar yok sayılır (karakter sınıfı içindekiler hariç) ve # ile başlayan kısımlar yorum olur. re.escape() ise kullanıcıdan gelen bir metni desene güvenle koymak için özel karakterleri kaçırır; aksi hâlde 1+1 gibi bir arama “bir veya daha fazla 1, ardından 1” anlamına gelir.
import re
POSTA = re.compile(r"""
(?P<kullanici>[\w.+-]+) # @ işaretinden önceki kısım
@
(?P<alan>[\w-]+(?:\.[\w-]+)+) # alan adı, en az bir nokta
""", re.VERBOSE)
for adres in ["deniz.k@ornek.com.tr", "hatali@adres", "x+etiket@mail.org"]:
m = POSTA.fullmatch(adres)
print(adres, "=>", m["alan"] if m else None)
# çıktı: deniz.k@ornek.com.tr => ornek.com.tr
# çıktı: hatali@adres => None
# çıktı: x+etiket@mail.org => mail.org
print(POSTA.groups, POSTA.groupindex)
# çıktı: 2 {'kullanici': 1, 'alan': 2}
print(re.escape("fiyat (TL): 9.99$"))
# çıktı: fiyat\ \(TL\):\ 9\.99\$
aranan = "1+1"
print(re.findall(re.escape(aranan), "1+1=2, 11=11"), re.findall(aranan, "1+1=2, 11=11"))
# çıktı: ['1+1'] ['11', '11']Bu e-posta deseni, gerçek dünyadaki tüm geçerli adresleri kapsamaya çalışmayan bilerek basit tutulmuş bir örnektir; amacı VERBOSE yazımını ve fullmatch() ile doğrulamayı göstermektir. Belgeler re.escape() için bir uyarı da yapar: bu fonksiyon değiştirme metni (sub() ikinci argümanı) için kullanılmamalıdır; orada yalnızca ters eğik çizgilerin kaçırılması gerekir.
Hatalı desenler ve Python 3.13-3.14 değişiklikleri
Geçersiz bir desen derlenirken re.PatternError fırlatılır. Bu sınıfın adı Python 3.13'te eski re.error adından değiştirildi; eski ad geriye uyumluluk için aynı sınıfa işaret etmeye devam ediyor. İstisnanın msg, pattern, pos, lineno ve colno öznitelikleri hatanın desendeki yerini verir. Belgelerin vurguladığı gibi bir metnin desenle eşleşmemesi asla hata değildir, sadece None döner.
import re
try:
re.compile(r"(\d+")
except re.PatternError as e:
print(type(e).__name__ + ":", e)
print(e.pos, re.error is re.PatternError)
# çıktı: PatternError: missing ), unterminated subpattern at position 0
# çıktı: 0 True
print(re.search(r"\bkedi\b", "kediler ve kedi"))
# çıktı: <re.Match object; span=(11, 15), match='kedi'>
print(re.search(r"satır$", "son satır\n"), re.search(r"satır\z", "son satır\n"))
# çıktı: <re.Match object; span=(4, 9), match='satır'> NoneSon satır Python 3.14'te gelen bir yeniliği kullanıyor. $ metnin sonunda ve ayrıca metnin sonundaki yeni satırın hemen öncesinde eşleşir; bu yüzden “son satır\n” metninde “satır$” eşleşti. Gerçekten yalnızca metnin mutlak sonunu istiyorsanız 3.14'ten beri \z kullanabilirsiniz; eski \Z aynı anlamdadır ve uyumluluk için korunur. What's New belgesi \z yazımının birçok başka regex motorunda belirsizlik olmadan aynı anlama geldiğini, \Z yazımının ise motorlar arasında ince farklar taşıdığını belirtir. Yine 3.14 ile \B boş metinle de eşleşir hâle geldi ve artık her zaman \b bayrağının tam tersidir.
Sık yapılan hatalar
- Deseni ham metin olmadan yazmak:
"\d+"birSyntaxWarningverir,"\b"ise sessizce geri silme karakteri olur ve kelime sınırı çalışmaz. Her zamanr"..."kullanın. - Metnin her yerinde ararken
match()kullanmak. Bu fonksiyon yalnızca başa bakar;search()gerekir. - Doğrulamada
search()ya da$ile yetinmek.fullmatch()metnin tamamını denetler. findall()sonucunun gruplara göre değiştiğini unutmak. Tek grup eklemek, sonucu tam eşleşmelerden grup içeriğine çevirir.- Sonucu denetlemeden
.group()çağırmak veNoneüzerindeAttributeErroralmak. - Kullanıcı girdisini
re.escape()olmadan desene eklemek. - Basit işler için regex kullanmak. Sabit metin aramak için
in,str.replace()vestr.split()yeterlidir.
Sık Sorulan Sorular
re.match ile re.search arasındaki fark nedir?
match() (3.15'teki yeni adıyla prefixmatch()) yalnızca metnin başında eşleşme arar; search() metnin herhangi bir yerinde arar. “Bu metin şununla mı başlıyor?” için ilki, “Bu metnin içinde şu var mı?” için ikincisi kullanılır.
re.match kaldırılacak mı?
Hayır. 3.15'te yumuşak kullanımdan kaldırıldı; bu, uyarı vermediği ve kaldırma planı olmadığı anlamına gelir. Yeni kodda daha açık olan prefixmatch() önerilir, ancak 3.14 ve öncesini desteklemeniz gerekiyorsa match() kullanmaya devam edebilirsiniz.
Desenleri her seferinde re.compile ile derlemeli miyim?
Zorunlu değil. Modül fonksiyonları son desenleri önbelleğe alır. Aynı deseni döngü içinde çok kez kullanıyorsanız ya da desene anlamlı bir ad vermek istiyorsanız compile() faydalıdır.
Türkçe harfler \w ile eşleşir mi?
Evet. str desenlerinde \w Unicode harflerini kapsar ve ş, ğ, ü, ö, ç, ı, İ ile eşleşir. Yalnızca re.ASCII bayrağını verirseniz bu harfler dışarıda kalır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · düzenli ifadeler başlangıç rehberi · string metotları · Unicode ve Türkçe karakterler · f-string biçimlendirme · Python 3.15 yenilikleri · csv modülü