ValueError dan KeyError menjelaskan kegagalan teknis, tetapi tidak menjelaskan kegagalan bisnis. Pesan “saldo tidak cukup” atau “pesanan sudah dikirim” adalah kegagalan domain yang layak punya nama sendiri agar kode pembaca bisa menangkapnya secara presisi. Python membolehkan kita mendefinisikan class exception sendiri. Pada Chapter ini kita akan membuat class exception pertama, menambahkan atribut kustom, lalu merancang hierarki error untuk satu aplikasi.
Materi ini memakai sintaks class di tingkat paling dasar. Pembahasan class secara menyeluruh — instance, method, inheritance — menempuh Section Object-Oriented Programming mulai dari Chapter Memahami Class dan Object.
Class Exception Pertama #
Resep minimalnya singkat: definisikan class yang mewarisi Exception, isi body dengan pass, selesai:
class SaldoTidakCukupError(Exception):
pass
def tarik_saldo(saldo, jumlah):
if jumlah > saldo:
raise SaldoTidakCukupError(
f"saldo {saldo} tidak cukup untuk penarikan {jumlah}"
)
return saldo - jumlah
try:
tarik_saldo(50000, 100000)
except SaldoTidakCukupError as e:
print("Transaksi gagal:", e)Output yang Dihasilkan:
Transaksi gagal: saldo 50000 tidak cukup untuk penarikan 100000Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1 (
class SaldoTidakCukupError(Exception):): class baru didefinisikan sebagai turunanException. Dengan mewarisiException, class ini otomatis bisa dibangkitkanraise, dicetak pesannya, dan ditangkap klausaexcept. - Baris 2 (
pass): body class tidak butuh isi tambahan; seluruh perilaku exception diwarisi dariException. - Baris 6–8 (
raise SaldoTidakCukupError(...)): instance dibuat dengan satu argumen string, lalu dibangkitkan saat jumlah penarikan melebihi saldo. - Baris 13 (
except SaldoTidakCukupError as e:): handler menangkap class buatan sendiri persis seperti class bawaan.
Menyimpan Data Tambahan dengan init #
Pesan string saja kadang kurang. Program penanganan pesanan butuh tahu ID pesanan mana yang bermasalah — bukan sekadar kalimat errornya. Method __init__ mengisi kebutuhan itu:
class PesananError(Exception):
def __init__(self, pesanan_id, pesan):
super().__init__(pesan)
self.pesanan_id = pesanan_id
def batalkan_pesanan(status):
raise PesananError(1024, f"pesanan berstatus {status} tidak bisa dibatalkan")
try:
batalkan_pesanan("dikirim")
except PesananError as e:
print("Pesan :", e)
print("ID pesanan :", e.pesanan_id)
print("Tipe :", type(e).__name__)Output yang Dihasilkan:
Pesan : pesanan berstatus dikirim tidak bisa dibatalkan
ID pesanan : 1024
Tipe : PesananErrorDua keputusan penting ada pada __init__:
super().__init__(pesan): constructor class induk dipanggil dengan pesan, sehingga mekanisme bawaanException(atributargssampai hasilstr(e)) tetap bekerja normal. Membacaetetap menghasilkan kalimat pesan yang rapi.self.pesanan_id = pesanan_id: data tambahan disimpan sebagai atribut instance. Pemanggil yang menangkap exception bisa membacae.pesanan_idtanpa mem-parsing string pesan.
Mekanisme __init__, super(), dan atribut instance akan dibedah tuntas pada Chapter Constructor dan init — pada titik ini kita cukup memakai polanya.
Merancang Hierarki Error Aplikasi #
Aplikasi nyata punya banyak jenis kegagalan. Praktik yang matang: sediakan satu class akar untuk seluruh error aplikasi, lalu turunkan class spesifik darinya. Pemanggil bisa memilih presisi tangkapan sesuai kebutuhannya:
class AplikasiError(Exception):
pass
class PembayaranError(AplikasiError):
pass
class SaldoTidakCukupError(PembayaranError):
pass
class KartuKedaluwarsaError(PembayaranError):
pass
def bayar(saldo, total):
if saldo < total:
raise SaldoTidakCukupError(f"saldo {saldo}, total tagihan {total}")
def checkout(saldo, total):
try:
bayar(saldo, total)
print("Pembayaran berhasil")
except SaldoTidakCukupError as e:
print("Tawarkan top-up saldo:", e)
except PembayaranError as e:
print("Tampilkan halaman error pembayaran:", e)
except AplikasiError as e:
print("Tampilkan error umum aplikasi:", e)
checkout(50000, 80000)Output yang Dihasilkan:
Tawarkan top-up saldo: saldo 50000, total tagihan 80000Struktur pewarisan di atas membentuk pohon: AplikasiError menjadi akar, PembayaranError menengahi kegagalan pembayaran, dan SaldoTidakCukupError menandai kasus spesifik. Aturan sub-class yang kita pelajari pada Chapter Menangani Beberapa Exceptions membuat fungsi checkout() bisa memilih — menangkap SaldoTidakCukupError untuk menawarkan top-up, atau cukup PembayaranError untuk semua masalah pembayaran sekaligus, atau AplikasiError sebagai jaring pengaman menyeluruh. Handler except SaldoTidakCukupError di contoh menangkap SaldoTidakCukupError beserta seluruh turunannya kelak.
Catatan tentang str dan repr #
Class Exception sudah menyediakan __str__ yang mengembalikan isi args — kita tidak perlu menulisnya sendiri. Method __repr__ bawaan pun sudah menghasilkan bentuk NamaClass('pesan') yang rapi:
class SaldoError(Exception):
pass
err = SaldoError("saldo tidak cukup")
print(err)
print(repr(err))Output yang Dihasilkan:
saldo tidak cukup
SaldoError('saldo tidak cukup')Mendefinisikan ulang __str__ hanya perlu bila class Anda mengubah cara pesan dibentuk. Untuk kasus umum — pesan utuh di argumen pertama — implementasi bawaan sudah memadai, dan menimpanya tanpa alasan kuat justru menghilangkan konsistensi dengan exception bawaan Python.
Satu pertanyaan tersisa setelah exception baru dibangkitkan dari dalam blok except: bagaimana menjaga jejak error asli? Jawabannya ada pada teknik chaining di Chapter Exception Chaining.