İçeriğe geç
academia.sh

Ders 16 / 16

Düzenli İfadeler

Örüntüdeki yedi açılış parantezinin ikisi yakalamaz ve geriye beş numaralı grup kalır; numara açılış parantezinin sırasına göre verilir, eşleşmeme bir istisna değil None'dır ve eşleşme nesnesi her grubun metnini, aralığını ve adını taşır.

İçindekiler

Önceki ders bu kursun ölçtüğü son sözdizim biçimini geride bıraktı. Şimdiye kadar her ölçüm dilin kendi protokollerini çağıran bir deyim ya da ifadeydi: for, if, +, with.

Bu dersin konusu ise dilin sözdizimi değildir. Düzenli ifade (regular expression) standart kitaplığın sunduğu ayrı bir küçük dildir: örüntü bir dizgide yazılır, ayrı bir kural kümesine göre yorumlanır ve sonuç bir nesne olarak geri döner. Örüntü eşleme algoritmalarının kendisi — kaba kuvvet arama, karma tabanlı arama, önek tablolu arama — Algoritmalar kursunun metin algoritmaları konusunda kuruldu ve burada tekrarlanmaz. Ölçülecek olan şey başka: eşleşme nesnesinin ne taşıdığı ve örüntüdeki parantezlerin hangi sırayla numaralandığı.

Örüntüde Parantezin İki İşi

Bir örüntüde parantezin iki ayrı işi vardır ve ikisi aynı yazımla görünür. Birincisi gruplama: içindeki parçayı tek bir birim yapar ki tekrar ya da seçim işleci ona uygulanabilsin. İkincisi yakalama: eşleşen metni ayrıca saklar ve numaralar.

Sıradan parantez her iki işi birden yapar. (?: ile açılan parantez yalnız gruplar, yakalamaz — numara almaz. (?P<ad> ile açılan parantez hem yakalar hem numarasının yanına bir ad koyar.

Numaralama kuralı tek cümledir: numara, açılış parantezinin soldan sağa sırasına göre verilir. İç içe parantezlerde bile dıştaki önce numaralanır, çünkü onun açılışı daha soldadır. Kapanış sırası hiç bakılmaz.

Grupların Numaralanması

Ölçüm bir ölçüm kaydı satırını çözen tek bir örüntü kurar: istasyon adı, yıl, isteğe bağlı ay, değer ve birim. Örüntü hem adlı hem yakalamayan parantez içerir.

  • HD43 — Parantez sayıları örüntü dizgisinin kendisinden sayılır; numaralanan grup sayısı ise derlenmiş örüntünün kendi alanından okunur. İkisi ayrı kaynaktır ve birbirini doğrular.
  • HD44 — Kâhin örüntüyü yazan kurgudur: hangi parçanın hangi gruba düşmesi gerektiği önceden bilinir.
  • HD45 — Aralıklar eşleşme nesnesinden okunur, elle sayılmaz; karakter aralığıdır, bayt değil.
  • HD46 — İsteğe bağlı grup eşleşmediğinde None döner; bu bir hata değil, grubun katılmadığının bildirimidir.
  • HD47 — İç içe örüntü ölçümü yalnız numaralamayı sınamak için ayrı tutulur; başka hiçbir şey ölçmez.
"""Yakalama gruplari: parantezlerin numaralanmasi ve eslesme nesnesinin tasidigi."""
import re

ORUNTU = r"(?P<istasyon>[a-z]+)-(\d{4})(?:-(\d{2}))?:\s*(-?\d+(?:[.,]\d+)?)\s*(°C|%)"
DERLI = re.compile(ORUNTU)

print(f"örüntüdeki açılış parantezi {ORUNTU.count('(')} | "
      f"yakalamayan parantez {ORUNTU.count('(?:')} | "
      f"adlı grup {ORUNTU.count('(?P<')} | numaralanan grup {DERLI.groups}")
print("ad -> numara:", DERLI.groupindex)

SATIRLAR = ("kuzey-2031: 12,5 °C", "yamac-2031-07: -3.5 °C",
            "ozet-2031: 88 %", "bozuk kayit")

print()
print(f"{'satır':<26s}{'eşleşti':<9s}{'aralık':<10s}{'son grup':>9s}   gruplar")
for s in SATIRLAR:
    e = DERLI.search(s)
    if e is None:
        print(f"  {s:<24s}{'hayır':<9s}{'-':<10s}{'-':>9s}   -")
    else:
        print(f"  {s:<24s}{'evet':<9s}{str(e.span()):<10s}"
              f"{e.lastindex:>9d}   {e.groups()}")

print()
e = DERLI.search(SATIRLAR[1])
print("ikinci satırın eşleşme nesnesi:")
print(f"  group(0) (tüm eşleşme): {e.group(0)!r}")
for i in range(1, DERLI.groups + 1):
    print(f"  group({i}): {e.group(i)!r}  aralık {e.span(i)}")
print(f"  adla erişim: {e.group('istasyon')!r} | "
      f"adlı grup sözlüğü: {e.groupdict()}")

print()
IC_ICE = re.compile(r"((\d{4})-(\d{2}))")
i = IC_ICE.search("yamac-2031-07: -3.5 °C")
print(f"iç içe örüntüde numaralanan grup {IC_ICE.groups}; "
      f"numara açılış parantezinin sırasına göre verilir:")
for n in range(1, IC_ICE.groups + 1):
    print(f"  group({n}) = {i.group(n)!r}")
örüntüdeki açılış parantezi 7 | yakalamayan parantez 2 | adlı grup 1 | numaralanan grup 5
ad -> numara: {'istasyon': 1}

satır                     eşleşti  aralık     son grup   gruplar
  kuzey-2031: 12,5 °C     evet     (0, 19)           5   ('kuzey', '2031', None, '12,5', '°C')
  yamac-2031-07: -3.5 °C  evet     (0, 22)           5   ('yamac', '2031', '07', '-3.5', '°C')
  ozet-2031: 88 %         evet     (0, 15)           5   ('ozet', '2031', None, '88', '%')
  bozuk kayit             hayır    -                 -   -

ikinci satırın eşleşme nesnesi:
  group(0) (tüm eşleşme): 'yamac-2031-07: -3.5 °C'
  group(1): 'yamac'  aralık (0, 5)
  group(2): '2031'  aralık (6, 10)
  group(3): '07'  aralık (11, 13)
  group(4): '-3.5'  aralık (15, 19)
  group(5): '°C'  aralık (20, 22)
  adla erişim: 'yamac' | adlı grup sözlüğü: {'istasyon': 'yamac'}

iç içe örüntüde numaralanan grup 3; numara açılış parantezinin sırasına göre verilir:
  group(1) = '2031-07'
  group(2) = '2031'
  group(3) = '07'

Eşleşme Nesnesinin Taşıdığı

İlk satır aritmetiği veriyor: örüntüde 7 açılış parantezi var, 2’si yakalamayan, geriye 5 numaralanan grup kalıyor. Sayının biri örüntü dizgisinden, öbürü derlenmiş örüntüden geldi ve tuttular. Adlı grubun numarası kaybolmuyor: istasyon adı 1 numaraya karşılık geliyor. Ad bir takma addır, numaranın yerine geçmez.

İkinci tablo dört satırı çözüyor. Üçünde eşleşme var, birinde yok. Eşleşenlerin gruplar sütunu beşerli demet veriyor ve isteğe bağlı ay grubu, ay yazılmamış satırlarda None dönüyor. None burada bir hata değildir: grubun katılmadığı anlamına gelir ve boş dizgiden ayrılır — boş dizgi “eşleşti ama içi boştu” demek olurdu.

Üçüncü blok eşleşme nesnesinin taşıdığı bilginin tamamını açıyor. group(0) tüm eşleşmedir ve bir grup değildir; numaralar ondan sonra başlar. Her grup hem metnini hem aralığını veriyor: 2031 kaydın 610 karakterleri arasında duruyor. Bu aralık, eşleşen metni kaynağın içinde yerinden çıkarmadan işaret etmeye yarar.

Son blok numaralamayı kesinleştiriyor. İç içe örüntüde 3 grup var ve group(1) dıştaki bütünü, group(2) yılı, group(3) ayı veriyor. Dıştaki parantez sonra kapanmasına rağmen önce numaralanıyor, çünkü önce açılıyor.

Aramanın Biçimleri

İkinci ölçüm aynı örüntüyü ayrı arama biçimleriyle çalıştırır ve eşleşmemenin ne ürettiğini sınar.

  • HD48 — Üç arama biçimi aynı derlenmiş örüntüyle ve aynı satırlarla denenir; tek değişen çağrılan yöntemdir.
  • HD49 — Çoklu arama sayıları listenin uzunluğundan okunur; satır sayısı metnin kendi satırlarından sayılır.
  • HD50 — Eşleşmeme durumunda dönen değer doğrudan basılır; sonucun istisna olmadığı koşumla gösterilir.
  • HD51 — Konum ölçümü aynı içeriği bir kez dizgi, bir kez bayt dizisi olarak arar; fark yalnız içeriğin türünden gelir.
"""Eslesme aramanin bicimleri, eslesmeme durumu ve dizgi ile bayt uzerinde konum."""
import re

ORUNTU = r"(?P<istasyon>[a-z]+)-(\d{4})(?:-(\d{2}))?:\s*(-?\d+(?:[.,]\d+)?)\s*(°C|%)"
DERLI = re.compile(ORUNTU)

DENEMELER = ("kuzey-2031: 12,5 °C", "  kuzey-2031: 12,5 °C",
             "kuzey-2031: 12,5 °C ek", "bozuk kayit")

print(f"{'satır':<28s}{'search':<9s}{'match':<8s}fullmatch")
for s in DENEMELER:
    d = [("evet" if a(s) else "hayır")
         for a in (DERLI.search, DERLI.match, DERLI.fullmatch)]
    print(f"  {repr(s):<26s}{d[0]:<9s}{d[1]:<8s}{d[2]}")

KAYITLAR = ("kuzey-2031: 12,5 °C\n"
            "yamac-2031-07: -3.5 °C\n"
            "ozet-2031: 88 %\n"
            "bozuk kayit\n"
            "guney-2032: 41 °C")

print()
bulunan = DERLI.findall(KAYITLAR)
yinelenen = list(DERLI.finditer(KAYITLAR))
print(f"metin satırı {len(KAYITLAR.splitlines())} | findall {len(bulunan)} | "
      f"finditer {len(yinelenen)} | findall öğesinin uzunluğu "
      f"{len(bulunan[0])} | grup sayısı {DERLI.groups}")
print("finditer aralıkları:", [e.span() for e in yinelenen])

print()
yok = DERLI.search("bozuk kayit")
print("eşleşmeyen aramanın sonucu:", yok, "| istisna fırlattı mı: hayır")
try:
    yok.group(0)
except AttributeError as e:
    print(f"sonuç üzerinde group çağrısı: {type(e).__name__} — "
          f"birinci dersin sınıflamasında program kusuru")

print()
print("değiştirme, grupları geri kullanır:")
print(" ", DERLI.sub(r"\g<istasyon>=\4\5", KAYITLAR).replace("\n", " | "))

print()
METIN = "kuzey yamaç: 12,5 °C"
HAM = METIN.encode("utf-8")
print(f"karakter {len(METIN)} | bayt {len(HAM)}")
print(f"dizgi üzerinde 'C' konumu {re.search(r'C', METIN).start()} | "
      f"bayt dizisi üzerinde {re.search(rb'C', HAM).start()}")
satır                       search   match   fullmatch
  'kuzey-2031: 12,5 °C'     evet     evet    evet
  '  kuzey-2031: 12,5 °C'   evet     hayır   hayır
  'kuzey-2031: 12,5 °C ek'  evet     evet    hayır
  'bozuk kayit'             hayır    hayır   hayır

metin satırı 5 | findall 4 | finditer 4 | findall öğesinin uzunluğu 5 | grup sayısı 5
finditer aralıkları: [(0, 19), (20, 42), (43, 58), (71, 88)]

eşleşmeyen aramanın sonucu: None | istisna fırlattı mı: hayır
sonuç üzerinde group çağrısı: AttributeError — birinci dersin sınıflamasında program kusuru

değiştirme, grupları geri kullanır:
  kuzey=12,5°C | yamac=-3.5°C | ozet=88% | bozuk kayit | guney=41°C

karakter 20 | bayt 22
dizgi üzerinde 'C' konumu 19 | bayt dizisi üzerinde 21

Sayıların Okunması

İlk tablo üç arama biçimini ayırıyor. search örüntüyü her yerde arar ve dört satırın üçünde buluyor. match yalnız baştan dener: önünde boşluk olan satırda başarısız oluyor. fullmatch satırın tamamının eşleşmesini ister: sonunda fazladan metin olan satırda başarısız oluyor. Aynı örüntü, aynı satır, üç ayrı yanıt — hangi soruyu sorduğunuza bağlı.

İkinci satır çoklu aramayı sayıyor: 5 satırlık metinde 4 eşleşme var ve findall ile finditer aynı sayıyı veriyor. Fark taşıdıkları şeydedir. findall her eşleşme için 5 uzunluğunda bir demet veriyor — yani yalnız grupların metnini; aralık ve ad bilgisi kayboluyor. finditer ise eşleşme nesnelerinin kendisini veriyor ve aralıklar okunabiliyor. Grup varken findall bir sadeleştirmedir ve sadeleştirdiği şey, önceki bölümde ölçülen bilginin tamamıdır.

Üçüncü blok eşleşmemenin ne olduğunu söylüyor: sonuç None ve istisna yok. Düzenli ifade eşleşmemeyi bir hata saymaz, çünkü eşleşmeme beklenen bir sonuçtur. İstisna ancak None üzerinde group çağrılırsa doğar ve o zaman gelen sınıf AttributeError olur — birinci dersin sınıflamasında bir program kusuru, yani except ile ele alınacak değil, sınamayla önlenecek bir şey. Arama sonucu kullanılmadan önce sınanır.

Değiştirme satırı grupların ikinci işini gösteriyor: yakalanan parçalar yerine konan metinde numarayla ya da adla geri kullanılabiliyor. Eşleşmeyen satır olduğu gibi kalıyor.

Son iki satır bu kursun üçüncü dersine bağlanıyor. Aynı içerik dizgi olarak 20 karakter, bayt dizisi olarak 22 bayt; aranan harfin konumu birinde 19, ötekinde 21. Düzenli ifade dizgide karakter, bayt dizisinde bayt sayar. Örüntü ile arandığı şeyin türü aynı olmalıdır ve dönen aralıklar o türün birimindedir.

Özet

  • Parantezin iki işi vardır: gruplama ve yakalama; (?: yalnız gruplar, (?P<ad> hem yakalar hem ad verir — örüntüdeki 7 açılış parantezinin 2’si yakalamaz, geriye 5 grup kalır.
  • Numara açılış parantezinin sırasına göre verilir; iç içe örüntüde dıştaki grup önce numaralanır ve group(0) bir grup değil, tüm eşleşmedir.
  • Eşleşme nesnesi her grubun metnini, aralığını ve varsa adını taşır; katılmayan isteğe bağlı grup None döner ve bu boş dizgiden ayrı bir bildirimdir.
  • search, match ve fullmatch aynı satırda ayrı yanıt verir; findall 5 öğelik demetler döndürüp aralık bilgisini düşürür, finditer eşleşme nesnesini korur.
  • Eşleşmeme bir istisna değil None’dır; istisna ancak sınanmadan group çağrılırsa doğar ve AttributeError olarak gelir.

Kurs Kapanışı

Bu kurs tek bir iddiayı on altı derste ölçtü: Python’da sözdizim bir kısaltmadır. Yazılan her biçim, adı belli bir özel yöntemi ya da adı belli bir kuralı çağırır; bir dersin sayısı, anlattığı sözdizimin hangi protokolü kaç kez çağırdığıdır.

ders anlatılan sözdizim çağrılan protokol
Python’un Yürütme Modeli on bir sözdizim biçimi 12 özel yöntem, toplam 18 çağrı
Sözdizim ve Girinti girinti, blok, deyim ve ifade özel yöntem yok: girinti birimleri ve ağaç
Değişkenler ve Veri Tipleri ad bağlama, x += ek __add__ ile __iadd__ ayrımı, nesne kimliği
İşleçler n + m, n += m, x in n __add__, __iadd__, __contains__ yoksa __iter__
Dizgilerle Çalışma dizgi yöntemleri ve biçimlendirme yazımları __format__, __str__, __repr__; değiştirilemezlik yeni nesne kurdurur
Tip Dönüşümü
Koşullar if n __bool__, yoksa __len__, o da yoksa doğru
Döngüler for x in n __iter__ ve __next__; bitişi StopIteration verir
Fonksiyon Tanımı def, varsayılan değer, argüman biçimleri varsayılan tanım anında bir kez değerlendirilir
Kapsam Kuralları ad okuma ve atama yerel, kapsayan, genel, yerleşik dört düzeyli arama
Yerleşik Fonksiyonlar
İstisnalar try, except, raise, finally istisna hiyerarşisi; yakalama sınıf soyağacına bakar
Özel İstisnalar class ...(Exception) sınıfın hiyerarşideki yeri yakalama davranışını belirler
Dosya İşlemleri open metin kipi ve ikili kip kodlama çözücüsü ve satır sonu çevirisi
Bağlam Yöneticileri with n __enter__, __exit__ ve dönüş değerinin yutma kararı
Düzenli İfadeler örüntü, grup, değiştirme eşleşme nesnesi ve yakalama grubu numaralaması

Tablonun boş satırları, bu ders yazıldığı sırada diskte bulunmayan derslerdir; onlar için satır uydurulmadı.

İkinci sütunla üçüncü sütun arasındaki mesafe kursun bütün bulgusudur. Aynı görünen iki yazım ayrı protokol çağırır — n + m ile n += m gibi. Aynı yazım, nesneye göre ayrı protokol çağırır — if n gibi. Ve bir protokolün başarısız olması da protokolün parçasıdır: döngüyü StopIteration bitirir, with bloğundaki istisnayı __exit__ görür ve dönüş değeriyle onu yutup yutmayacağına karar verir.

Buradan çıkan son okuma şudur: bir nesnenin ne yapabildiği, tanımladığı özel yöntemlerle belirlenir; tipinin adıyla değil. Kurs boyunca ölçülen nesne hiçbir yerleşik koleksiyondan inmedi; for içine girebilmesinin, in ile sınanabilmesinin, with bloğunda kullanılabilmesinin tek gerekçesi ilgili yöntemleri tanımlamış olmasıydı.

Bu kurs boyunca protokoller hep kullanıldı. Bir sonraki kurs olan Veri Yapıları ve Fonksiyonel Araçlar işin öteki yarısını alır: protokolü yazmak. İkisi ayrı şeylerdir. for yazan biri yineleme protokolünün çağıranıdır; kendi yineleyicisini, kendi üretecini, kendi sıralama ölçütünü yazan biri ise protokolün karşı tarafını kurar. Listeler, demetler, sözlükler ve kümeler orada birer veri yapısı olarak değil, birer protokol gerçekleştirimi olarak ele alınır — ve kendi nesnenizi onların yerine geçirebilmenizin koşulu, bu kursta sayılan yöntemleri kendinizin tanımlamasıdır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat