Saat program gagal, interpreter Python tidak hanya berhenti diam; interpreter Python mencetak laporan lengkap bernama traceback ke stderr. Bagi pemula, laporan tersebut sering terlihat seperti dinding teks yang menakutkan. Padahal traceback justru berisi informasi paling berharga untuk debugging: jalur eksekusi yang ditempuh, baris persis yang gagal, dan jenis error yang terjadi. Pada Chapter ini kita akan mempelajari cara membaca traceback dengan urutan yang benar, mengenali error yang paling sering muncul, dan menafsirkan traceback berantai.
Urutan Membaca: dari Bawah ke Atas #
Kebiasaan pertama yang perlu dibangun: baca traceback dari baris paling bawah. Baris terakhir selalu berisi nama exception dan pesan errornya, sedangkan baris-baris di atasnya berisi jalur panggilan yang menuju ke titik kegagalan. Simpan kode berikut sebagai kasir.py, lalu jalankan dengan python3 kasir.py:
def hitung_harga_satuan(total, jumlah):
return total / jumlah
def proses_pesanan(total, jumlah):
return hitung_harga_satuan(total, jumlah)
print(proses_pesanan(150000, 0))Output yang Dihasilkan (dicetak interpreter Python ke stderr):
Traceback (most recent call last):
File "kasir.py", line 9, in <module>
print(proses_pesanan(150000, 0))
^^^^^^^^^^^^^^^^^^^^^^^^^
File "kasir.py", line 6, in proses_pesanan
return hitung_harga_satuan(total, jumlah)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "kasir.py", line 2, in hitung_harga_satuan
return total / jumlah
~~~~~~^~~~~~~~
ZeroDivisionError: division by zeroAlur membacanya ada dua arah. Untuk mengetahui error apa, baca dari bawah: baris terakhir menyebut ZeroDivisionError: division by zero. Untuk mengetahui bagaimana sampai di situ, baca dari atas ke bawah: modul utama memanggil proses_pesanan() di baris 9, proses_pesanan() memanggil hitung_harga_satuan() di baris 6, lalu pembagian di baris 2 gagal. Setiap entri File ... seperti itu disebut satu frame, dan anatomi setiap frame selalu sama:
| Bagian frame | Makna |
|---|---|
File "kasir.py", line 2, in hitung_harga_satuan | nama file, nomor baris, dan function yang sedang berjalan |
return total / jumlah | isi baris sumber yang sedang dieksekusi |
~~~~~~^~~~~~~~ | penanda yang menyorot bagian ekspresi yang memicu error |
Pada contoh di atas, tanda ~ dan ^ menunjuk operator / beserta kedua operannya — sinyal kuat bahwa pembagian di baris tersebut adalah biang keladi, bukan kode di function pemanggilnya.
Tiga Error yang Sering Muncul #
Sebagian besar traceback harian berasal dari segelintir exception yang sama. Tiga contoh berikut mewakili pola yang paling sering dijumpai. Pertama, KeyError saat kunci dictionary tidak ada — simpan sebagai laporan.py:
penjualan = {"teh": 120, "kopi": 85}
def ambil_penjualan(produk):
return penjualan[produk]
print(ambil_penjualan("susu"))Output yang Dihasilkan (dicetak interpreter Python ke stderr):
Traceback (most recent call last):
File "laporan.py", line 8, in <module>
print(ambil_penjualan("susu"))
^^^^^^^^^^^^^^^^^^^^^^^
File "laporan.py", line 5, in ambil_penjualan
return penjualan[produk]
~~~~~~~~~^^^^^^^^
KeyError: 'susu'Kedua, TypeError saat tipe data tidak cocok — simpan sebagai struk.py:
def format_label(nama, jumlah):
return nama + " x " + jumlah
print(format_label("Kopi Gayo", 3))Output yang Dihasilkan (dicetak interpreter Python ke stderr):
Traceback (most recent call last):
File "struk.py", line 5, in <module>
print(format_label("Kopi Gayo", 3))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "struk.py", line 2, in format_label
return nama + " x " + jumlah
~~~~~~~~~~~~~^~~~~~~~
TypeError: can only concatenate str (not "int") to strPesan can only concatenate str (not "int") to str langsung memberi tahu object mana yang salah tipe: nilai 3 bertipe int padahal operator + pada string hanya menerima string. Solusinya adalah konversi dengan str(jumlah) atau berpindah ke f-string. Ketiga, ZeroDivisionError seperti pada kasir.py di atas. Ketiganya mengikuti pola yang sama: baris terakhir menyebut jenis kesalahan, penanda ~/^ menunjuk operasi yang gagal, dan sisanya memetakan jalur panggilannya.
Traceback Berantai #
Kadang satu error terjadi saat interpreter Python sedang menangani error lain. Hasilnya adalah dua traceback yang tersambung. Simpan kode berikut sebagai gudang.py:
stok = {"teh": 12}
try:
jumlah = stok["kopi"]
except KeyError:
print(f"Stok kopi habis: {jumlah}")Output yang Dihasilkan (dicetak interpreter Python ke stderr):
Traceback (most recent call last):
File "gudang.py", line 4, in <module>
jumlah = stok["kopi"]
~~~~^^^^^^^^
KeyError: 'kopi'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "gudang.py", line 6, in <module>
print(f"Stok kopi habis: {jumlah}")
^^^^^^
NameError: name 'jumlah' is not definedFrasa During handling of the above exception berarti error kedua (NameError) lahir di dalam blok except yang sedang menangani error pertama (KeyError) — penugasan jumlah tidak pernah terjadi karena baris 4 gagal lebih dulu.
Bentuk kedua adalah rantai eksplisit lewat raise ... from e. Simpan berikut sebagai pembayaran.py:
class DataPesananError(Exception):
pass
def muat_total(teks):
try:
return int(teks)
except ValueError as e:
raise DataPesananError(f"total tidak valid: {teks!r}") from e
print(muat_total("lima ribu"))Output yang Dihasilkan (dicetak interpreter Python ke stderr):
Traceback (most recent call last):
File "pembayaran.py", line 7, in muat_total
return int(teks)
^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'lima ribu'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "pembayaran.py", line 12, in <module>
print(muat_total("lima ribu"))
^^^^^^^^^^^^^^^^^^^^^^^
File "pembayaran.py", line 9, in muat_total
raise DataPesananError(f"total tidak valid: {teks!r}") from e
DataPesananError: total tidak valid: 'lima ribu'Frasa The above exception was the direct cause of the following exception menyatakan hubungan yang disengaja: error ValueError di atas sengaja dibungkus menjadi DataPesananError yang lebih bermakna bagi pemanggilnya, dan klausa from e menjadikan e sebagai penyebab resmi. Mekanisme lengkap kedua bentuk rantai ini sudah kita bedah tuntas pada Chapter Exception Chaining.
Daftar Periksa Membaca Traceback #
Saat menatap traceback, jalankan urutan pemeriksaan berikut:
- Baca baris paling bawah: exception apa dan apa pesannya?
- Baca frame paling bawah: file dan baris mana yang gagal?
- Amati penanda
~dan^: bagian ekspresi mana yang disorot? - Baca frame berikutnya ke arah atas bila perlu: siapa pemanggilnya?
- Buka file pada baris tersebut dan periksa nilai variabel di sekitarnya.
Dengan kemampuan membaca traceback, langkah berikutnya adalah memilih alat bantu: kapan cukup memakai print(), dan kapan saatnya naik kelas ke debugger interaktif.