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:

bash
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.

python
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.

python
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().

python
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.

python
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.

python
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.

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.

python
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.

python
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.

python
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.