Pendalaman pattern matching

Pattern matching untuk destructuring data bersarang, guard, dan parsing nyata.

Daftar isi

Pernyataan match membandingkan sebuah nilai terhadap pola (pattern), mengikat bagian-bagian yang cocok ke nama baru, lalu menjalankan cabang yang sesuai. Kemampuan destrukturisasi data bersarang inilah yang membedakannya dari pencabangan if biasa, dan tidak ditemukan pada perbandingan kesetaraan. Topik ini membahas pola sequence dan mapping bersarang, guard yang menambah syarat, pola OR, serta penerapannya pada penguraian baris log.

Membongkar data bersarang#

Pola sequence membongkar list dan tuple menurut posisi, sedangkan pola mapping membongkar dictionary menurut kunci. Keduanya dapat disarangkan, dan simbol * atau ** menampung sisanya. Nama yang dituliskan pada pola otomatis menjadi variabel yang berisi bagian data yang cocok.

python Contoh: pola mapping bersarang dengan sisa kunci
data = {"aksi": "kirim", "muatan": {"penerima": "Budi", "jumlah": 3, "label": ["fragil", "dingin"]}}

match data:
    case {"aksi": "kirim", "muatan": {"penerima": penerima, "jumlah": jumlah, **sisanya}}:
        print("mengirim ke", penerima, "sebanyak", jumlah)
        print("kunci lain:", sorted(sisanya))
    case _:
        print("format tidak dikenal")
text Keluaran
mengirim ke Budi sebanyak 3
kunci lain: ['label']

Pola di atas menuntut kunci aksi bernilai literal "kirim" dan kunci muatan berbentuk dictionary dengan dua kunci tertentu. Nama penerima dan jumlah berperan sebagai pola tangkap yang mengikat nilainya, sedangkan **sisanya mengumpulkan kunci yang tidak disebutkan. Pola sequence dengan * bekerja dengan cara yang sama untuk list.

python Contoh: pola sequence dengan sisa elemen
riwayat = ["mulai", 3, 5, 7, "selesai"]
match riwayat:
    case ["mulai", *tengah, "selesai"]:
        print(tengah)
    case _:
        print("tidak cocok")
text Keluaran
[3, 5, 7]

Pola ini menuntut elemen pertama bernilai "mulai" dan elemen terakhir bernilai "selesai", sedangkan seluruh elemen di antaranya dikumpulkan ke tengah berapa pun panjangnya. Kombinasi pembatas tetap di ujung dan pengumpul di tengah cocok untuk data berformat tetap dengan isi yang bervariasi, seperti rekam jejak eksekusi.

Guard dan pola OR#

Pola hanya mengenali bentuk data, sedangkan syarat nilai yang lebih bebas ditempatkan pada guard, yaitu klausa if yang mengikuti pola. Cabang hanya dipilih bila polanya cocok dan guardnya benar, dan bila guardnya salah, pencocokan berlanjut ke cabang berikutnya. Pola OR memperluas pilihan dengan memperbolehkan beberapa pola berbagi satu cabang melalui tanda |.

python Contoh: guard pada pola tangkap
for nilai in (78, 92, 55):
    match nilai:
        case n if n >= 85:
            print(n, "-> A")
        case n if n >= 70:
            print(n, "-> B")
        case _:
            print(n, "-> C")
text Keluaran
78 -> B
92 -> A
55 -> C

Nilai 78 tidak lolos guard pertama yang menuntut 85 ke atas, sehingga pencocokan turun ke guard berikutnya dan berakhir di kategori B. Urutan cabang dengan demikian menjadi bagian dari logika, sebagaimana if/elif. Pada pola OR, urutan justru tidak berpengaruh karena semua pilihannya setara.

Guard bukan tempat penanganan exception. Ekspresi yang menimbulkan error di dalam guard meneruskan error itu ke luar pernyataan match, alih-alih dianggap pencocokan yang gagal. Karena itu, pemanggilan fungsi yang bisa gagal sebaiknya diuji terlebih dahulu sebelum dipakai sebagai guard.

python Contoh: pola OR untuk beberapa literal
for perintah in ["maju", "stop", "keluar", "unduh"]:
    match perintah:
        case "stop" | "keluar":
            print(perintah, "-> penghentian")
        case "maju" | "mundur":
            print(perintah, "-> pergerakan")
        case _:
            print(perintah, "-> tidak dikenal")
text Keluaran
maju -> pergerakan
stop -> penghentian
keluar -> penghentian
unduh -> tidak dikenal

Setiap literal pada kelompok pertama memilih cabang penghentian, sehingga literal dalam kelompok itu tidak perlu ditulis dalam case tersendiri. Guard dan pola OR dapat digabung pada satu cabang, misalnya case [x, y] if x > y, yang menuntut polanya cocok dan syaratnya terpenuhi sekaligus.

Program utuh: pengurai baris log#

Contoh berikut menerapkan keseluruhan fitur pada pekerjaan yang umum, yaitu mengurai baris log berformat terbatas. Setiap baris dipecah menjadi kata, lalu dicocokkan dengan pola sequence yang menggambarkan formatnya, lengkap dengan guard untuk validasi angka dan pola tangkap * untuk isi bebas.

python urai_log.py
baris_log = [
    "SET suhu 28",
    "NAIK suhu 4",
    "HAPUS suhu",
    "CATATAN gudang dibuka pukul tujuh",
    "SET tekanan puluhan",
    "RESET",
]

for baris in baris_log:
    bagian = baris.split()
    match bagian:
        case ["SET", kunci, nilai] if nilai.isdigit():
            print("menetapkan", kunci, "=", int(nilai))
        case ["SET", kunci, nilai]:
            print("nilai", nilai, "bukan angka, kunci", kunci, "diabaikan")
        case ["NAIK", kunci, nilai]:
            print("menaikkan", kunci, "sebanyak", int(nilai))
        case ["HAPUS", kunci]:
            print("menghapus", kunci)
        case ["CATATAN", *isi]:
            print("catatan:", " ".join(isi))
        case []:
            print("baris kosong dilewati")
        case _:
            print("baris tidak dikenal:", baris)
text Keluaran
menetapkan suhu = 28
menaikkan suhu sebanyak 4
menghapus suhu
catatan: gudang dibuka pukul tujuh
nilai puluhan bukan angka, kunci tekanan diabaikan
baris tidak dikenal: RESET

Simpan berkas di atas di direktori kerja, lalu jalankan dengan perintah berikut.

bash
# menjalankan program pengurai baris log
python3 urai_log.py
text Keluaran
menetapkan suhu = 28
menaikkan suhu sebanyak 4
menghapus suhu
catatan: gudang dibuka pukul tujuh
nilai puluhan bukan angka, kunci tekanan diabaikan
baris tidak dikenal: RESET

Setiap baris log jatuh ke cabang yang tepat tanpa rantai if bertingkat. Baris SET suhu 28 lolos guard isdigit(), sedangkan SET tekanan puluhan berbentuk sama tetapi gagal guard, sehingga jatuh ke cabang SET kedua yang menangani nilai tidak valid. Baris CATATAN memakai pola tangkap *isi agar isi bebasnya tidak perlu dihitung, dan baris RESET ditampung cabang case _ sebagai penampung segala bentuk yang tersisa.

Kesalahan umum#

Kesalahan pertama adalah memperlakukan nama pada pola sebagai perbandingan terhadap variabel yang sudah ada. Pola case status merupakan pola tangkap yang mengikat nilai apa pun, sehingga cabangnya selalu terpilih dan variabel luar tertimpa.

python Contoh: pola tangkap yang disangka perbandingan
status = "berhenti"
for perintah in ["jalan", "berhenti"]:
    match perintah:
        case status:
            print("pola tangkap menangkap", status)

class Sinyal:
    BERHENTI = "berhenti"

for perintah in ["jalan", "berhenti"]:
    match perintah:
        case Sinyal.BERHENTI:
            print("pola nilai mengenali", perintah)
        case _:
            print("bukan perintah berhenti:", perintah)
text Keluaran
pola tangkap menangkap jalan
pola tangkap menangkap berhenti
bukan perintah berhenti: jalan
pola nilai mengenali berhenti

Dua baris pertama memperlihatkan bahwa case status menangkap "jalan" yang seharusnya tidak cocok, sekaligus menimpa variabel status luar. Perbaikannya pada perulangan kedua memakai pola nilai bertitik Sinyal.BERHENTI, yang dibandingkan dengan nilai perintah dan tidak mengikat apa pun. Aturan praktisnya, nama tunggal selalu berarti tangkap, sedangkan perbandingan membutuhkan literal atau nama bertitik.

Kesalahan kedua adalah lupa menyediakan cabang penampung. Apabila tidak ada pola yang cocok dan case _ tidak tersedia, pernyataan match berlalu tanpa menjalankan apa pun, dan program berlanjut tanpa keluaran maupun error. Keheningan semacam ini sulit dilacak pada data yang bentuknya bervariasi. Karena itu, cabang case _ yang mencatat nilai tak dikenal layak disertakan pada setiap match yang menerima masukan luar, seperti pada program pengurai log di atas.

Ringkasan#

  • Pola sequence dan mapping membongkar list serta dictionary menurut posisi dan kunci, dengan * dan ** menampung sisanya.
  • Guard if menambah syarat nilai pada satu cabang, sedangkan pola OR melalui | menghimpun beberapa pola pada cabang yang sama.
  • Nama tunggal pada pola selalu berarti pola tangkap, sehingga perbandingan dengan nilai yang sudah ada membutuhkan literal atau nama bertitik.
  • match tanpa cabang yang cocok tidak menjalankan apa pun, sehingga cabang case _ menjadi penampung yang aman untuk masukan tak dikenal.

Referensi#