Klausa except tidak mencocokkan nama secara ajaib — kecocokan ditentukan oleh posisi class dalam satu pohon keluarga. KeyError bisa tertangkap klausa LookupError karena KeyError memang anak dari LookupError. Tanpa peta pohon tersebut, kita hanya menebak efek dari setiap handler yang ditulis. Pada Chapter ini kita akan memetakan pohon class exception Python, memahami status khusus KeyboardInterrupt dan SystemExit, serta mengenali exception bawaan yang paling sering dijumpai.

Pohon Class Exception #

Seluruh class exception berakar pada BaseException. Pohon berikut menampilkan bagian yang paling sering dipakai sehari-hari:

text
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   ├── OverflowError
    │   └── FloatingPointError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── ValueError
    │   └── UnicodeError
    │       ├── UnicodeDecodeError
    │       └── UnicodeEncodeError
    ├── TypeError
    ├── AttributeError
    ├── NameError
    │   └── UnboundLocalError
    ├── OSError
    │   ├── FileNotFoundError
    │   ├── PermissionError
    │   └── TimeoutError
    ├── RuntimeError
    │   ├── NotImplementedError
    │   └── RecursionError
    ├── ImportError
    │   └── ModuleNotFoundError
    ├── StopIteration
    └── AssertionError

Cara membacanya: setiap class anak adalah kasus khusus dari class orang tuanya. FileNotFoundError adalah OSError yang terjadi karena file tidak ditemukan; ZeroDivisionError adalah ArithmeticError yang terjadi karena pembagi nol. Pohon di atas disederhanakan — Python masih menyimpan class lain seperti BaseExceptionGroup, tetapi peta ini sudah mencakup hampir seluruh kebutuhan sehari-hari.

Pohon ini menjelaskan mekanisme pencocokan klausa except yang sudah kita amati pada Chapter Menangani Beberapa Exceptions. Ketika sebuah exception dibangkitkan, interpreter Python tidak hanya membandingkan nama class-nya dengan klausa — interpreter Python menelusuri rantai leluhur class tersebut dari bawah ke atas. Klausa except LookupError: cocok untuk KeyError karena LookupError ada di rantai leluhur KeyError; klausa except Exception: cocok untuk hampir semuanya karena Exception adalah leluhur besar di dekat akar pohon.

Menguji Hubungan Antar Class #

Function bawaan issubclass() menjawab pertanyaan “apakah class A adalah turunan dari class B?” — pertanyaan yang sama persis dengan yang diajukan interpreter Python saat mencocokkan klausa except:

python
print(issubclass(KeyError, LookupError))
print(issubclass(IndexError, LookupError))
print(issubclass(LookupError, Exception))
print(issubclass(Exception, BaseException))
print(issubclass(KeyboardInterrupt, Exception))

Output yang Dihasilkan:

text
True
True
True
True
False

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

  1. Baris 1 (issubclass(KeyError, LookupError)): KeyError berada di bawah LookupError di pohon, jadi hasilnya True — inilah sebabnya klausa except LookupError: menangkap KeyError.
  2. Baris 2 (issubclass(IndexError, LookupError)): saudara KeyError juga True, karena keduanya satu orang tua.
  3. Baris 3–4 (issubclass(LookupError, Exception) dan issubclass(Exception, BaseException)): rantai leluhur berlanjut sampai akar pohon.
  4. Baris 5 (issubclass(KeyboardInterrupt, Exception)): False — KeyboardInterrupt menggantung langsung di bawah BaseException, di luar cabang Exception. Fakta ini punya konsekuensi praktis pada bagian berikutnya.

Kenapa Menangkap Exception, Bukan BaseException #

Python sengaja menaruh KeyboardInterrupt dan SystemExit di luar cabang Exception. KeyboardInterrupt dibangkitkan ketika pengguna menekan Ctrl+C; SystemExit dibangkitkan function sys.exit() untuk mengakhiri program dengan kode status. Keduanya adalah sinyal kontrol program, bukan kegagalan operasi. Karena keduanya bukan turunan Exception, klausa except Exception: secara otomatis melewatkan keduanya.

Bayangkan konsekuensinya bila jaring pengaman menangkap sinyal tersebut. Demo berikut memperlihatkan except BaseException: menelan SystemExit:

python
import sys

try:
    sys.exit(1)
except BaseException as e:
    print("Tertangkap:", type(e).__name__)

print("Program masih berjalan seolah tidak terjadi apa-apa")

Output yang Dihasilkan:

text
Tertangkap: SystemExit
Program masih berjalan seolah tidak terjadi apa-apa

Function sys.exit(1) seharusnya menghentikan program dengan kode status 1. Karena klausa except BaseException: menangkap SystemExit, permintaan berhenti tersebut dibatalkan dan program lanjut berjalan. Efek yang sama menimpa Ctrl+C bila KeyboardInterrupt tertangkap — program menjadi sulit dihentikan secara sopan.

Satu class sisanya, GeneratorExit, adalah komunikasi internal antara interpreter Python dan generator — topik yang akan kita bahas pada Section Decorators dan Generators. Ketiganya berbagi satu filosofi: sinyal kendali program bukanlah kegagalan operasi, sehingga ketiganya dijauhkan dari cabang Exception agar jaring pengaman except Exception: tidak menghalangi sinyal tersebut.

Exception Bawaan yang Paling Sering Ditemui #

Sepuluh class berikut mencakup mayoritas traceback yang akan Anda jumpai. Kolom contoh pemicu memperlihatkan operasi yang membangkitkan masing-masing class:

ExceptionMuncul saatContoh pemicu
ValueErrortipe data tepat, tetapi nilainya tidak masuk akalint("abc")
TypeErroroperasi diterapkan pada tipe yang tidak didukung1 + "2"
KeyErrorkunci dictionary tidak ditemukandata["tak-ada"]
IndexErrorindex berada di luar rentang koleksi[1, 2][5]
FileNotFoundErrorpath file yang dibuka tidak adaopen("file-tak-ada.txt")
ZeroDivisionErrorpembagian atau modulo dengan nol1 / 0
NameErrornama belum pernah didefinisikanprint(belum_ada)
AttributeErrorobject tidak memiliki atribut atau method itu"abc".append(1)
ModuleNotFoundErrormodule yang di-import tidak tersediaimport modul_tak_ada
RuntimeErrorkegagalan umum yang tidak cocok kategori lainbervariasi

Dua catatan penutup untuk tabel di atas. StopIteration hampir tidak pernah Anda tangkap secara manual — class ini dipakai iterator untuk memberi sinyal habisnya data saat perulangan for, sebagaimana dibahas pada materi iterasi di Section Comprehensions dan Iterators. Sedangkan ModuleNotFoundError adalah turunan ImportError: tangkap ImportError bila ingin bereaksi terhadap kedua bentuk kegagalan import sekaligus.

Pengetahuan pohon ini bukan sekadar trivia. Saat merancang penanganan error untuk aplikasi nyata, posisi class dalam pohon menentukan seberapa luas jaring pengaman kita bekerja — pertimbangan yang menjadi jantung Chapter Merancang Error Handling.