Waktu saya pertama menulis parser untuk command-line tool kecil, hasilnya memalukan: rantai if/elif sepanjang puluhan baris, penuh split(), pengecekan panjang list, dan casting manual yang gampang salah indeks. Python 3.10 merilis structural pattern matching lewat sintaks match dan case, dan sejak itu saya hampir tidak pernah kembali ke pola lama untuk urusan beginian. Di tutorial ini kamu bakal menguasai match/case dari nol, lengkap dengan contoh yang benar-benar jalan.
Prasyarat: Python 3.10 ke Atas
match dan case hanya tersedia di Python 3.10 ke atas. Cek versimu dulu:
python --version
Kalau versi Python-mu masih 3.9 ke bawah, sintaks match akan memicu SyntaxError. Upgrade dulu sebelum lanjut — tutorial ini tidak jalan di versi lama.
Sintaks Dasar: Literal dan Wildcard
Bentuk paling sederhana adalah mencocokkan nilai literal. match mengevaluasi satu ekspresi, lalu Python memeriksa case dari atas ke bawah dan berhenti di pola pertama yang cocok. case _ adalah wildcard yang menangkap apa pun — selalu taruh di paling bawah sebagai jalur aman.
def arti_status(kode):
match kode:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Kode tidak dikenal"
print(arti_status(404)) # Not Found
print(arti_status(200)) # OK
print(arti_status(999)) # Kode tidak dikenal
Perhatikan: tidak ada break atau fall-through seperti switch di C. Setiap case sudah eksklusif, dan hanya satu cabang yang dieksekusi.
Gabungkan Beberapa Nilai dengan Pola OR
Beberapa nilai yang menuju aksi yang sama bisa digabung dengan |. Lebih ringkas daripada menulis tiga case terpisah.
def kategori_status(kode):
match kode:
case 200 | 201 | 204:
return "Sukses"
case 401 | 403:
return "Akses ditolak"
case 404:
return "Tidak ditemukan"
case 500 | 502 | 503:
return "Error server"
case _:
return "Kode lain"
print(kategori_status(403)) # Akses ditolak
print(kategori_status(503)) # Error server
Menangkap Nilai dengan Capture Pattern
Inilah yang membuat match/case jauh lebih kuat dari if/elif: nama variabel di dalam pola tidak dibandingkan, melainkan menangkap nilai pada posisi itu. Di contoh berikut, nama, a, dan b otomatis terikat ke elemen hasil split().
def proses_perintah(perintah):
parts = perintah.split()
match parts:
case ["help"]:
return "Perintah: help, greet <nama>, add <a> <b>, quit"
case ["greet", nama]:
return f"Halo, {nama}!"
case ["add", a, b]:
return f"Hasil: {float(a) + float(b)}"
case ["quit"]:
return "Sampai jumpa!"
case _:
return "Perintah tidak dikenali"
print(proses_perintah("greet Budi")) # Halo, Budi!
print(proses_perintah("add 3 4")) # Hasil: 7.0
print(proses_perintah("help")) # Perintah: help, greet <nama>, add <a> <b>, quit
Jebakan paling umum: nama variabel dalam pola selalu menangkap, bukan merujuk ke variabel yang sudah ada. Kalau kamu menulis case status: dengan harapan cocok dengan isi variabel status, yang terjadi justru pola itu menangkap apa pun. Untuk membandingkan dengan nilai variabel, pakai guard clause atau konstanta bertitik seperti HTTPStatus.NOT_FOUND.
Mencocokkan Sequence: List dan Tuple
Pola seperti ["greet", nama] cocok dengan list yang panjangnya persis dua dan elemen pertamanya "greet". Mau lebih fleksibel? Pakai * untuk menampung sisa elemen: [pertama, *sisa] cocok dengan list apa pun yang punya minimal satu elemen.
def ambil_pertama(daftar):
match daftar:
case []:
return None
case [pertama, *sisa]:
return (pertama, sisa)
print(ambil_pertama([])) # None
print(ambil_pertama([10, 20, 30, 40])) # (10, [20, 30, 40])
Mencocokkan Mapping: Kamus dan Data JSON
Untuk kamus atau payload JSON, pola mapping mencocokkan berdasarkan kunci, bukan urutan. Ini berguna untuk menangani event, webhook, atau respons API yang strukturnya beragam.
def tangani_event(event):
match event:
case {"type": "login", "user": user}:
return f"{user} masuk"
case {"type": "logout", "user": user}:
return f"{user} keluar"
case {"type": "error", "message": pesan}:
return f"Error: {pesan}"
case _:
return "Event tidak dikenali"
print(tangani_event({"type": "login", "user": "budi"})) # budi masuk
print(tangani_event({"type": "error", "message": "DB timeout"})) # Error: DB timeout
Kunci yang tidak disebut dalam pola diabaikan, jadi kamus dengan field tambahan tetap cocok. Kalau kamu butuh sisa kunci, tambahkan **sisa di akhir pola.
Mencocokkan Instance Kelas
Pola class mencocokkan atribut dari __init__. Untuk kelas biasa, pakai pola keyword seperti Point(x=0, y=0) — jauh lebih ekspresif daripada isinstance beruntun. Belum paham class dan __init__? Baca dulu panduan OOP Python.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def lokasi(p):
match p:
case Point(x=0, y=0):
return "Titik asal"
case Point(x=0, y=y):
return f"Sumbu Y, y={y}"
case Point(x=x, y=0):
return f"Sumbu X, x={x}"
case Point(x=x, y=y) if x > 0 and y > 0:
return f"Kuadran I ({x}, {y})"
case _:
return "Kuadran lain"
print(lokasi(Point(0, 0))) # Titik asal
print(lokasi(Point(0, 5))) # Sumbu Y, y=5
print(lokasi(Point(3, 4))) # Kuadran I (3, 4)
print(lokasi(Point(-1, 2))) # Kuadran lain
Mencocokkan Dataclass
Dataclass dan match/case adalah pasangan yang serasi. Field dataclass punya urutan tetap, jadi bisa dicocokkan secara posisional atau keyword. Kalau belum kenal dataclass, tutorial dataclass ini jadi titik awal yang pas.
from dataclasses import dataclass
@dataclass
class Pesanan:
id: int
status: str
total: float
def proses_pesanan(p):
match p:
case Pesanan(status="pending", total=t):
return f"Menunggu pembayaran sebesar {t:,.0f}"
case Pesanan(status="paid", total=t) if t > 1_000_000:
return "Perlu review manual"
case Pesanan(status="paid", total=t):
return f"Langsung dikirim, total {t:,.0f}"
case Pesanan(status="cancelled"):
return "Dibatalkan"
case _:
return "Status tidak dikenal"
print(proses_pesanan(Pesanan(1, "pending", 250000))) # Menunggu pembayaran sebesar 250,000
print(proses_pesanan(Pesanan(2, "paid", 1500000))) # Perlu review manual
print(proses_pesanan(Pesanan(3, "paid", 300000))) # Langsung dikirim, total 300,000
Menambah Syarat dengan Guard Clause
Pola saja kadang tidak cukup. Guard clause if ... di akhir case menambah syarat yang dievaluasi setelah pola cocok. Di contoh dataclass di atas, if t > 1_000_000 memisahkan pesanan besar dari yang biasa. Guard juga jalan keluar untuk membandingkan dengan nilai variabel, karena di sinilah kamu boleh memakai variabel dari luar pola.
def tebak(nilai, target):
match nilai:
case x if x == target:
return "Tepat!"
case x if abs(x - target) <= 2:
return "Dekat"
case _:
return "Melenceng"
print(tebak(5, 5)) # Tepat!
print(tebak(4, 5)) # Dekat
print(tebak(20, 5)) # Melenceng
Studi Kasus: Evaluator Ekspresi Rekursif
Biar makin terasa manfaatnya, ini contoh nyata: evaluator ekspresi matematika berbentuk list bersarang, mirip struktur AST. Pola sequence plus rekursi menangani parsing yang biasanya butuh puluhan baris if/elif.
def evaluasi(expr):
match expr:
case int(n) | float(n):
return n
case ["+", a, b]:
return evaluasi(a) + evaluasi(b)
case ["-", a, b]:
return evaluasi(a) - evaluasi(b)
case ["*", a, b]:
return evaluasi(a) * evaluasi(b)
case ["/", a, b]:
return evaluasi(a) / evaluasi(b)
case _:
raise ValueError(f"Ekspresi tidak valid: {expr}")
print(evaluasi(["*", ["+", 1, 2], 5])) # 15
print(evaluasi(["/", ["*", 6, 7], 3])) # 14.0
Setiap cabang operator memanggil evaluasi lagi untuk anak-anaknya, dan pola int(n) | float(n) menghentikan rekursi di daun. Parsing yang tadinya kamu tulis manual jadi deklaratif dan nyaris bebas bug indeks.
Kapan Pakai match/case, Kapan Cukup if/elif
Jangan dipaksa. Kalau cuma membandingkan satu variabel dengan beberapa nilai literal, if/elif tetap oke. match/case bersinar saat strukturnya majemuk — kombinasi tipe, bentuk, dan nilai sekaligus, atau saat kamu butuh menangkap beberapa variabel dalam satu langkah. Parser, penanganan event, state machine, dan pemrosesan payload JSON adalah medan terbaiknya.
Mulai dari satu fungsi yang rantai if/elif-nya sudah menyakitkan, lalu refactor dengan match/case. Bandingkan panjang dan keterbacaannya — biasanya langsung terasa bedanya.