Neden yeni bir dallanma aracı?
Koşullar dersinde match'i kısaca gördün: bir değeri birkaç sabitle karşılaştırmanın düzenli yolu. Ama match aslında bundan çok daha güçlüdür. Adı yapısal desen eşleme (structural pattern matching): yalnızca "değer şuna eşit mi" diye sormaz, "veri şu şekilde mi" diye sorar ve şekle uyuyorsa parçalarını tek hamlede değişkenlere ayırır.
Ilgaz Bank'ın işlem kuyruğunu düşün. Kuyruğa gelen her mesaj bir sözlüktür ama türü farklıdır: havalede alıcı IBAN'ı ve tutar vardır, fatura ödemesinde kurum kodu, kart işleminde kart numarası ve taksit sayısı. if/elif ile yazarsan her dalda önce türü kontrol eder, sonra anahtarların var olup olmadığına bakar, sonra değerleri tek tek çekersin. match bunların üçünü tek satırda yapar.
Temel söz dizimi ve değer desenleri
def kargo_durumu(kod):
match kod:
case "H":
return "Hazırlanıyor"
case "K":
return "Kargoda"
case "T":
return "Teslim edildi"
case _:
return "Bilinmeyen kod"
for k in ["H", "T", "X"]:
print(k, "→", kargo_durumu(k))
match'ten sonraki ifadeye konu (subject) denir. Her case bir desen (pattern) taşır; desenler yukarıdan aşağıya denenir ve ilk eşleşen çalışır, sonrakilere bakılmaz. C ya da Java'daki switch'ten farklı olarak "düşme" (fall-through) yoktur, break yazmazsın. _ joker (wildcard) desendir: her şeyle eşleşir, bu yüzden en sona konur ve else görevini görür. Hiçbir desen eşleşmezse ve _ yoksa match sessizce hiçbir şey yapmaz.
Değer desenleri (literal patterns) sayı, metin, True, False ve None olabilir. Sayılarda == ile, True/False/None'da is ile karşılaştırılır.
Birleşik desen: |
Birkaç değer aynı sonuca gidiyorsa onları | ("veya") ile birleştirirsin.
def gun_tipi(gun):
match gun.lower():
case "cumartesi" | "pazar":
return "Hafta sonu: şube kapalı"
case "pazartesi" | "salı" | "çarşamba" | "perşembe" | "cuma":
return "İş günü"
case _:
return "Geçersiz gün"
print(gun_tipi("Pazar"))
print(gun_tipi("salı"))
print(gun_tipi("bayram"))
Yakalama deseni ve en önemli tuzak
Desende tırnaksız, noktasız bir ad yazarsan Python onu karşılaştırılacak bir değer olarak değil, yakalanacak bir değişken olarak görür. Yakalama deseni (capture pattern) her şeyle eşleşir ve konuyu o ada bağlar.
ONAY = "onay"
def durum_eski(durum):
match durum:
case "iptal":
return "İptal"
case ONAY: # DİKKAT: bu bir karşılaştırma DEĞİL
return "Onaylandı (" + ONAY + ")"
print(durum_eski("onay"))
print(durum_eski("beklemede")) # yanlışlıkla "Onaylandı" der!
case ONAY: satırı "durum ONAY'a eşit mi" diye sormaz; "ne gelirse gelsin, adı ONAY olsun" der. İkinci çağrıda "beklemede" bu dala girer ve ONAY artık "beklemede"dir. Arkasından başka bir case yazsaydın Python bunu fark edip SyntaxError verirdi; en sonda olduğu için sessizce yanlış çalışır.
case "onay":) ya da noktalı ad kullan: case Durum.ONAY:. Noktalı adlar (sınıf özelliği, enum üyesi, modül sabiti) değer deseni sayılır. Düz ad her zaman yakalamadır.Guard: desene ek koşul
Bazen şekil yetmez, değere bir koşul da koymak istersin. case desen if koşul: yazımına guard (koruma) denir. Desen eşleşir, değişkenler bağlanır, sonra koşul denetlenir; koşul yanlışsa sıradaki case'e geçilir.
def tutar_sinifi(tutar):
match tutar:
case 0:
return "Boş işlem"
case t if t < 0:
return "İade"
case t if t >= 50_000:
return "Yüksek tutar: onay gerekir"
case _:
return "Normal"
for t in [0, -120, 75_000, 900]:
print(t, tutar_sinifi(t))
Guard'lı dallarda sıra önemlidir: daha özel koşulu üste yaz. Genel bir desen (case t:) üstte kalırsa altındaki özel dallar hiç çalışmaz.
Dizi desenleri: komut ayrıştırıcı
Konu bir liste ya da demetse eleman sayısına ve elemanlara göre eşleştirebilirsin. [x, y] tam iki elemanlı dizilerle eşleşir ve elemanları x ile y'ye bağlar. *rest "geri kalanların hepsi" demektir (demet açmadaki yıldızlı ad gibi).
def komut(satir):
match satir.split():
case []:
return "Boş komut"
case ["yardım"] | ["help"]:
return "Komutlar: ekle, sil, listele"
case ["listele"]:
return "Sepet listeleniyor"
case ["ekle", urun]:
return f"{urun} sepete eklendi (1 adet)"
case ["ekle", urun, adet] if adet.isdigit():
return f"{urun} sepete eklendi ({adet} adet)"
case ["sil", *urunler] if urunler:
return f"Silindi: {', '.join(urunler)}"
case [bilinmeyen, *_]:
return f"Bilinmeyen komut: {bilinmeyen}"
for s in ["", "help", "ekle çay", "ekle çay 3", "ekle çay üç", "sil çay kahve", "uç"]:
print(repr(s), "→", komut(s))
Bu örnekte birçok kavram birlikte çalışıyor: [] boş listeyle, ["yardım"] | ["help"] iki tek elemanlı listeden biriyle eşleşir; urun ve adet yakalanır; guard (adet.isdigit()) "üç" gibi geçersiz adedi dışarıda bırakır ve son dala düşürür. *_ geri kalanı yakalamadan yok sayar.
match "abc": case [a, b, c]: eşleşmez; Python bu karışıklığı önlemek için str, bytes ve bytearray'i dizi desenlerinden hariç tutar. Metni parçalara ayırmak istiyorsan önce split() ile listeye çevir.Sözlük desenleri: banka işlem yönlendirme
JSON benzeri mesajlar için en kullanışlı desen budur. {"tip": "havale", "tutar": t} deseni, konu bir sözlükse, "tip" anahtarı "havale" ise ve "tutar" anahtarı varsa eşleşir; tutarı t'ye bağlar. Desende yazmadığın fazladan anahtarlar yok sayılır; bu, sürekli alan eklenen gerçek mesajlarla çalışırken çok işe yarar.
def yonlendir(islem):
match islem:
case {"tip": "havale", "tutar": t, "alici": alici} if t > 50_000:
return f"Uyum ekibine: {alici} için {t} TL havale"
case {"tip": "havale" | "eft", "tutar": t, "alici": alici}:
return f"Transfer: {alici} ← {t} TL"
case {"tip": "fatura", "kurum": kurum, "tutar": t}:
return f"Fatura ödemesi: {kurum} {t} TL"
case {"tip": "kart", "taksit": int(n)} if n > 1:
return f"Taksitli kart işlemi ({n} taksit)"
case {"tip": "kart"}:
return "Tek çekim kart işlemi"
case {"tip": tip}:
return f"Desteklenmeyen işlem türü: {tip}"
case _:
return "Geçersiz mesaj"
islemler = [
{"tip": "havale", "tutar": 75_000, "alici": "TR12", "kanal": "mobil"},
{"tip": "eft", "tutar": 1_200, "alici": "TR34"},
{"tip": "fatura", "kurum": "Su", "tutar": 310},
{"tip": "kart", "taksit": 6},
{"tip": "kart", "taksit": 1},
{"tip": "kripto"},
["havale", 100],
]
for i in islemler:
print(yonlendir(i))
Desenler iç içe girebilir: "havale" | "eft" bir sözlük deseninin içinde birleşik desendir, int(n) ise "değer int olsun ve n'ye bağlansın" diyen bir sınıf desenidir. Liste olarak gelen son mesaj hiçbir sözlük deseniyle eşleşmez ve joker dala düşer. Geri kalan anahtarları toplamak istersen **diger yazabilirsin: case {"tip": "havale", **diger}:.
case {"tip": ("havale" | "eft") as tur, "tutar": t}: yazarsan hangi alternatifin eşleştiğini tur değişkeninde bulursun. as, bir desenin tamamını bir ada bağlar.Sınıf desenleri: dataclass ile
Konu bir nesneyse türüne ve özelliklerine göre eşleştirebilirsin. int(), str(), float() gibi yerleşik türler zaten sınıf desenidir. Kendi sınıfların için en kolay yol dataclass'tır (orta seviyede ayrıntılı göreceksin; burada yalnızca kısa bir kayıt tanımlamak için kullanıyoruz). dataclass, alanlarını sırayla desene açar; böylece konumsal yazabilirsin.
from dataclasses import dataclass
@dataclass
class Havale:
alici: str
tutar: float
@dataclass
class Fatura:
kurum: str
tutar: float
def ozet(islem):
match islem:
case Havale(alici, tutar) if tutar > 10_000:
return f"Büyük havale → {alici}"
case Havale(alici=a):
return f"Havale → {a}"
case Fatura(kurum="Elektrik" | "Doğalgaz", tutar=t):
return f"Enerji faturası: {t}"
case Fatura():
return "Diğer fatura"
case int() | float() as tutar:
return f"Ham tutar: {tutar}"
case _:
return "Tanınmadı"
for i in [Havale("TR12", 25_000), Havale("TR34", 50), Fatura("Elektrik", 640.5), Fatura("Su", 90), 42, "x"]:
print(ozet(i))
Havale(alici, tutar) konumsal, Havale(alici=a) anahtar sözcüklü yazımdır. Fatura() boş parantezle "türü Fatura olsun, alanları önemli değil" demektir. Parantezsiz case Fatura: yazmak ise yine yakalama tuzağıdır.
match mi if/elif mi?
| Durum | Tercih | Neden |
|---|---|---|
| Bir değeri birkaç sabitle karşılaştırmak | İkisi de olur | 3-4 dalda if/elif yeterince açık; daha fazlasında match okunur |
| Aralık ve eşik (tutar >= 500) | if/elif | match'te her dal guard ister, kazanç yok |
| Birden fazla değişkene bağlı karmaşık mantık | if/elif | match tek bir konu üzerine kuruludur |
| Listenin uzunluğuna ve elemanlarına göre dallanma | match | Uzunluk kontrolü ve açma tek desende |
| JSON/sözlük mesajın türüne göre yönlendirme | match | Anahtar varlığı, değer ve çekme bir arada |
| Nesnenin türüne ve alanlarına göre işlem | match | isinstance + özellik okuma tek satırda |
| Python 3.9 ve öncesini desteklemesi gereken kod | if/elif | match 3.10'da geldi; eski sürümde SyntaxError |
Desen türlerinin özeti
| Desen | Örnek | Eşleşir |
|---|---|---|
| Değer | case 404: | Konu 404'e eşitse |
| Noktalı değer | case Durum.ONAY: | Sabite eşitse |
| Yakalama | case x: | Her zaman; x'e bağlar |
| Joker | case _: | Her zaman; bağlamaz |
| Birleşik | case 301 | 302: | Alternatiflerden biri |
| Dizi | case [x, *rest]: | En az bir elemanlı liste/demet |
| Sözlük | case {"tip": t}: | "tip" anahtarı olan sözlük |
| Sınıf | case Havale(tutar=t): | Havale örneği |
| as | case [_, _] as cift: | Desen + tümünü bağlama |
Enum ile güvenli sabitler
Durum kodlarını metin olarak dağıtmak yerine enum.Enum tanımlarsan hem yazım hatası riskini azaltır hem de noktalı adla güvenle eşleştirirsin.
from enum import Enum
class Durum(Enum):
BEKLIYOR = "bekliyor"
ONAY = "onay"
RED = "red"
def mesaj(d):
match d:
case Durum.ONAY:
return "Kredi onaylandı"
case Durum.RED:
return "Başvuru reddedildi"
case Durum.BEKLIYOR:
return "İnceleniyor"
print(mesaj(Durum("onay")), "|", mesaj(Durum.BEKLIYOR))
Performans ve sınırlar
match bir "atlama tablosu" değildir; desenler sırayla denenir, yani if/elif zincirinden hızlı değildir. Avantajı hız değil okunabilirlik ve doğruluktur. Yüzlerce sabit değere göre dallanıyorsan sözlükle eşleme ({"H": "Hazırlanıyor", ...}.get(kod)) hem daha hızlı hem daha kısadır. Ayrıca derleyici, eksik dalları (exhaustiveness) denetlemez; tüm durumları kapsadığından emin olmak için sonda case _: ile açıkça hata fırlatmak iyi bir alışkanlıktır.