Satu blok kode sering punya lebih dari satu cara untuk gagal: data masukan bisa salah format, pembagian bisa bernilai nol, kunci bisa hilang. Kalau setiap kegagalan ditangani dengan try sendiri-sendiri, kode cepat membengkak. Pada Chapter ini kita akan mempelajari tiga teknik untuk menangani beberapa exception sekaligus: tuple dalam satu klausa, beberapa klausa terpisah, serta pemanfaatan hubungan parent-child antar class exception.

Satu Handler untuk Beberapa Exception #

Beberapa kegagalan kadang cukup ditanggapi dengan reaksi yang sama. Caranya: tulis beberapa class dalam bentuk tuple setelah except:

python
def baca_persentase(teks):
    try:
        angka = int(teks)
        print("Nilai:", 100 / angka)
    except (ValueError, ZeroDivisionError) as e:
        print("Input tidak valid:", e)

baca_persentase("lima")
baca_persentase("0")
baca_persentase("4")

Output yang Dihasilkan:

text
Input tidak valid: invalid literal for int() with base 10: 'lima'
Input tidak valid: division by zero
Nilai: 25.0

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 3 (angka = int(teks)): pada pemanggilan pertama, string "lima" gagal dikonversi sehingga ValueError dibangkitkan.
  2. Baris 4 (print("Nilai:", 100 / angka)): pada pemanggilan kedua, konversi "0" berhasil, tetapi pembagian 100 / 0 membangkitkan ZeroDivisionError.
  3. Baris 5 (except (ValueError, ZeroDivisionError) as e:): interpreter Python memeriksa keanggotaan class exception dalam tuple. ValueError dan ZeroDivisionError sama-sama cocok, jadi satu handler menangani keduanya.
  4. Baris 6 (print("Input tidak valid:", e)): pesan dari masing-masing exception tercetak. Pemanggilan ketiga lolos tanpa error dan mencetak 25.0.

Tuple yang terlalu panjang adalah sinyal bahwa handler mungkin mencampur kegagalan yang seharusnya ditangani berbeda.

Handler Terpisah untuk Respons Berbeda #

Ketika tiap kegagalan butuh reaksi berbeda, tulis beberapa klausa except berurutan. Interpreter Python memeriksa klausa dari atas ke bawah dan menjalankan handler pertama yang cocok — klausa lain dilewati:

python
def ambil_nilai(data, kunci):
    try:
        print(f"Nilai {kunci} adalah {data[kunci]}")
    except KeyError:
        print(f"Kunci '{kunci}' tidak ditemukan")
    except TypeError:
        print("Kunci harus bisa di-hash")

stok = {"kopi": 12, "teh": 8}

ambil_nilai(stok, "teh")
ambil_nilai(stok, "susu")
ambil_nilai(stok, ["kopi"])

Output yang Dihasilkan:

text
Nilai teh adalah 8
Kunci 'susu' tidak ditemukan
Kunci harus bisa di-hash

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 3 (print(f"Nilai {kunci} ...")): akses data["teh"] berhasil sehingga handler tidak terpakai.
  2. Baris 4 (except KeyError:): akses data["susu"] membangkitkan KeyError: 'susu' karena kunci memang tidak ada; handler pertama cocok dan berjalan.
  3. Baris 6 (except TypeError:): list ["kopi"] tidak bisa di-hash, sehingga dictionary menolaknya sebagai kunci dengan membangkitkan TypeError; handler pertama tidak cocok, handler kedua yang berjalan.

Urutan klausa bekerja seperti rantai elif yang sudah kita bahas pada Chapter if, elif, dan else: hanya cabang pertama yang cocok yang dieksekusi.

Class Parent Menangkap Class Anak #

Pencocokan klausa except tidak berhenti pada nama class yang sama. Sebuah klausa cocok apabila class exception yang muncul sama dengan atau merupakan turunan (sub-class) dari class yang dituliskan. Inilah sebabnya except LookupError: berhasil menangkap KeyError sekaligus IndexError:

python
def ambil_aman(data, kunci, default=None):
    try:
        return data[kunci]
    except LookupError:
        return default

pelanggan = ["Budi", "Siti"]
stok = {"kopi": 12, "teh": 8}

print(ambil_aman(pelanggan, 5, "tidak ada"))
print(ambil_aman(stok, "susu", 0))

Output yang Dihasilkan:

text
tidak ada
0

Pada pemanggilan pertama, pelanggan[5] membangkitkan IndexError; pada pemanggilan kedua, stok["susu"] membangkitkan KeyError. Keduanya adalah sub-class dari LookupError, jadi satu klausa menangkap keduanya. Peta lengkap hubungan parent-child antar class exception akan kita petakan pada Chapter Exception Hierarchy.

Urutan Klausa Menentukan Hasil #

Konsekuensi aturan sub-class: klausa parent yang ditulis lebih dulu akan menangkap semua exception sebelum klausa child sempat diperiksa. Kode berikut sah secara sintaks, tetapi handler ZeroDivisionError tidak akan pernah berjalan:

python
def proses(nilai):
    try:
        return 100 / int(nilai)
    except Exception:
        print("Ditangkap klausa Exception (parent)")
    except ZeroDivisionError:
        print("Ditangkap klausa ZeroDivisionError (child)")

proses("0")

Output yang Dihasilkan:

text
Ditangkap klausa Exception (parent)

Interpreter Python tidak menganggap klausa yang tak terjangkau sebagai error — kode berjalan seolah-olah tidak ada masalah. Inilah bug halus yang sulit terlihat tanpa uji. Urutan yang benar meletakkan class paling spesifik di atas:

python
def proses(nilai):
    try:
        return 100 / int(nilai)
    except ZeroDivisionError:
        print("Ditangkap ZeroDivisionError (child)")
    except Exception as e:
        print("Ditangkap Exception:", e)

print(proses("0"))
print(proses("nol"))

Output yang Dihasilkan:

text
Ditangkap ZeroDivisionError (child)
None
Ditangkap Exception: invalid literal for int() with base 10: 'nol'
None

Pemanggilan proses("0") kini dijawab oleh handler ZeroDivisionError, sedangkan proses("nol") jatuh ke handler Exception yang lebih umum. Nilai None tercetak karena kedua handler tidak mengembalikan nilai apa pun — perilaku function tanpa return yang kita bahas pada Chapter Return Value.

Dengan tiga teknik ini, satu blok try bisa merespons banyak skenario kegagalan secara terkendali. Berikutnya kita lengkapi pernyataan try dengan dua klausa opsional: else dan finally.