Statement match/case yang diperkenalkan Python 3.10 sering disebut “switch-case milik Python”, padahal kemampuannya jauh lebih besar: match tidak membandingkan nilai, melainkan mencocokkan struktur data dan mengikat bagian-bagianya ke variabel. Materi dasar pola literal dan pencocokan sederhana telah dibahas pada Chapter match-case. Pada Chapter ini kita naik level ke pola yang menjadikan structural pattern matching layak dipakai di kode produksi: sequence pattern, mapping pattern, class pattern, serta kombinasi OR, AS, dan guard.

Sequence dan Mapping Pattern untuk Payload #

Data nyata biasanya berbentuk struktur bersarang: list berisi dictionary, dictionary berisi list. Pattern sequence ([a, b]) dan pattern mapping ({"kunci": nilai}) mencocokkan bentuk tersebut dan langsung membongkar isinya. Contoh berikut menangani payload event mirip JSON:

python
def tangani(event):
    match event:
        case {"type": "klik", "pos": [x, y]}:
            return f"klik pada ({x}, {y})"
        case {"type": "ketik", "teks": str() as isi, "shift": True}:
            return f"ketik huruf besar: {isi.upper()}"
        case {"type": "ketik", "teks": str() as isi}:
            return f"ketik: {isi}"
        case {"type": "scroll", "arah": arah, "jumlah": n} if n > 0:
            return f"scroll {arah} sebanyak {n}"
        case _:
            return "event tidak dikenal"


events = [
    {"type": "klik", "pos": [120, 40]},
    {"type": "ketik", "teks": "a", "shift": True},
    {"type": "ketik", "teks": "z"},
    {"type": "scroll", "arah": "bawah", "jumlah": 3},
    {"type": "gestur"},
]
for e in events:
    print(tangani(e))

Output yang Dihasilkan:

text
klik pada (120, 40)
ketik huruf besar: A
ketik: z
scroll bawah sebanyak 3
event tidak dikenal

Mari kita bedah pola-pola pada function tangani:

  1. Baris 3 (case {"type": "klik", "pos": [x, y]}): pattern mapping mencocokkan dictionary yang memiliki kunci "type" bernilai "klik" dan kunci "pos" berupa sequence dua elemen; elemen tersebut diikat ke x dan y baru setelah seluruh struktur cocok.
  2. Baris 5 (case {"type": "ketik", "teks": str() as isi, "shift": True}): sub-pola str() memastikan nilai bertipe string, as isi mengikatnya, dan "shift": True hanya cocok bila nilainya benar-benar True — urutan case menentukan bahwa varian shift dicek lebih dulu.
  3. Baris 9 (case ... if n > 0:): guard if menjalankan kondisi Python biasa setelah struktur cocok; event scroll dengan jumlah nol atau negatif jatuh ke case berikutnya.

Dua aturan penting di balik layar: pattern sequence hanya cocok untuk sequence (list, tuple), bukan set atau string; dan pattern mapping hanya cocok untuk mapping — kunci ekstra pada data tetap diperbolehkan, karena pencocokan bersifat sebagian, bukan menyeluruh.

Class Pattern dengan match_args #

Class pattern memungkinkan pencocokan berdasarkan tipe dan atribut. Atribut posisi pada pola Titik(0, y) dibaca dari atribut class __match_args__ — tuple yang menyatakan urutan atribut yang boleh dibongkar:

python
class Titik:
    __match_args__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y


def deskripsi(titik):
    match titik:
        case Titik(0, 0):
            return "titik asal"
        case Titik(0, y):
            return f"pada sumbu Y, y={y}"
        case Titik(x, 0):
            return f"pada sumbu X, x={x}"
        case Titik(x, y):
            return f"titik bebas: ({x}, {y})"


print(deskripsi(Titik(0, 0)))
print(deskripsi(Titik(0, 7)))
print(deskripsi(Titik(3, 0)))
print(deskripsi(Titik(2, 5)))

Output yang Dihasilkan:

text
titik asal
pada sumbu Y, y=7
pada sumbu X, x=3
titik bebas: (2, 5)

Pada baris case Titik(0, y), interpreter Python memanggil isinstance(titik, Titik) terlebih dahulu, lalu membaca titik.x dan titik.y sesuai urutan __match_args__ untuk dicocokkan dengan 0 dan diikat ke y. Tanpa __match_args__, kita masih bisa memakai pola atribut bernama seperti case Titik(x=0, y=y) — hasilnya sama, hanya lebih panjang ditulis.

OR Pattern, AS Pattern, dan Guard #

Tiga penyusun kecil yang sering dipakai bersama: OR pattern | mengizinkan beberapa alternatif dalam satu case, AS pattern as mengikat nilai yang dicocokkan (bukan bagian isinya), dan guard menjalankan kondisi tambahan:

python
def tanggapi_status(status):
    match status:
        case 200 | 201 | 204:
            return "sukses"
        case 404 | 410 as kode:
            return f"sumber daya tidak ditemukan ({kode})"
        case int() as kode if kode >= 500:
            return f"galat server ({kode})"
        case _:
            return "status tidak dikenal"


for s in (200, 404, 503, 302):
    print(s, "->", tanggapi_status(s))

Output yang Dihasilkan:

text
200 -> sukses
404 -> sumber daya tidak ditemukan (404)
503 -> galat server (503)
302 -> status tidak dikenal

Pada baris case 404 | 410 as kode, operator as mengikat keseluruhan nilai subject (404) ke kode — berbeda dari pola sequence yang mengikat bagian-bagian internal. Pola int() adalah class pattern tanpa argumen yang hanya menguji tipe, sehingga case int() as kode if kode >= 500 membaca “jika status bertipe int apa pun dan nilainya >= 500”.

Capture Bukan Literal: Jebakan Nama Polos #

Perbedaan paling berbahaya bagi pemula: nama polos di dalam case adalah pola capture, bukan pembanding nilai. Nama yang memuat titik (misalnya Kode.OK) dibaca sebagai nilai, sedangkan nama tanpa titik mengikat apa pun:

python
class Kode:
    OK = 200
    HILANG = 404


def dengan_nama_dotted(kode):
    match kode:
        case Kode.OK:  # dotted name dibaca sebagai nilai 200
            return "nilai konstanta: sukses"
        case Kode.HILANG:
            return "nilai konstanta: hilang"
        case nilai:  # nama polos sebagai case terakhir: capture
            return f"tertangkap: {nilai}"


print(dengan_nama_dotted(200))
print(dengan_nama_dotted(404))
print(dengan_nama_dotted(500))

Output yang Dihasilkan:

text
nilai konstanta: sukses
nilai konstanta: hilang
tertangkap: 500

Interpreter Python bahkan aktif melindungi kita dari versi keliru. Kode berikut menolak dikompilasi karena capture di posisi awal membuat case berikutnya mustahil tercapai:

python
def dengan_nama_polos(kode):
    match kode:
        case kode_ok:  # nama polos SELALU menangkap nilai
            return "selalu cocok"
        case 500:
            return "tidak pernah tercapai"

Output yang Dihasilkan (dicetak interpreter Python ke stderr):

text
  File "demo.py", line 3
    case kode_ok:  # nama polos SELALU menangkap nilai
         ^^^^^^^
SyntaxError: name capture 'kode_ok' makes remaining patterns unreachable

Kapan match Kalah dari if/elif #

Pencocokan pola berjalan berurutan dari atas ke bawah seperti if/elif, dan setiap pengujian pola memanggil isinstance serta pengambilan atribut — bukan magic hash lookup. Untuk membandingkan satu nilai terhadap beberapa literal sederhana, if/elif biasa sama jelasnya dan tidak menambah mesin baru. match baru unggul ketika ada struktur untuk dibongkar: payload bersarang, tipe berbeda-beda, atau kombinasi nilai dan bentuk. Jika sebuah case hanya berisi case x: lalu if x == ... di dalam badannya, itu sinyal kuat pola sedang dipaksa dan if/elif akan lebih jernih.