Ana içeriğe geç
Kesit Python
Temel · Veri yapıları

match-case: yapısal desen eşleme

Python 3.10 ile gelen match-case ile değer, birleşik, dizi, sözlük ve sınıf desenlerini; guard ve yakalamayı öğrenir, komut ayrıştırıcı ve banka işlem yönlendirici yazarsın.

Uygulamada aç, 17 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

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.

Sabitle karşılaştırmak için nokta gerekirBir sabitle karşılaştırmak istiyorsan ya değeri doğrudan yaz (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.

Metin dizi deseniyle eşleşmezMetin teknik olarak bir dizi olsa da 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}:.

as ile alt deseni adlandırcase {"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?

DurumTercihNeden
Bir değeri birkaç sabitle karşılaştırmakİkisi de olur3-4 dalda if/elif yeterince açık; daha fazlasında match okunur
Aralık ve eşik (tutar >= 500)if/elifmatch'te her dal guard ister, kazanç yok
Birden fazla değişkene bağlı karmaşık mantıkif/elifmatch tek bir konu üzerine kuruludur
Listenin uzunluğuna ve elemanlarına göre dallanmamatchUzunluk kontrolü ve açma tek desende
JSON/sözlük mesajın türüne göre yönlendirmematchAnahtar varlığı, değer ve çekme bir arada
Nesnenin türüne ve alanlarına göre işlemmatchisinstance + özellik okuma tek satırda
Python 3.9 ve öncesini desteklemesi gereken kodif/elifmatch 3.10'da geldi; eski sürümde SyntaxError
Gerçek işteBir bankanın ya da e-ticaret sitesinin arka planında olay güdümlü (event-driven) sistemler çalışır: mesaj kuyruğundan "sipariş_oluştu", "ödeme_alındı", "iade_talebi" gibi türü farklı JSON olaylar gelir ve her biri farklı bir işleyiciye yönlendirilir. Webhook alıcıları, sohbet botlarının komut ayrıştırıcıları, CLI araçları ve basit yorumlayıcılar da aynı işi yapar. Bu kodu match ile yazmak, kod incelemesinde (code review) "hangi mesaj nereye gidiyor" sorusunu tek bakışta yanıtlatır; yeni bir olay türü eklemek tek bir case eklemektir.
Uzmanlaş

Desen türlerinin özeti

DesenÖrnekEşleşir
Değercase 404:Konu 404'e eşitse
Noktalı değercase Durum.ONAY:Sabite eşitse
Yakalamacase x:Her zaman; x'e bağlar
Jokercase _:Her zaman; bağlamaz
Birleşikcase 301 | 302:Alternatiflerden biri
Dizicase [x, *rest]:En az bir elemanlı liste/demet
Sözlükcase {"tip": t}:"tip" anahtarı olan sözlük
Sınıfcase Havale(tutar=t):Havale örneği
ascase [_, _] 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.

Alıştırmalara geç (17)