25. Menangani Exception dengan Elegan
Pada Bab 24, kita belajar cara membaca dan memahami exception saat terjadi. Sekarang kita akan belajar cara menangani exception dengan elegan, sehingga program kita bisa pulih dari error alih-alih crash. Ini penting untuk menulis program yang tangguh, ramah pengguna, dan dapat menghadapi situasi tak terduga.
Saat sebuah exception terjadi di Python, alur normal program langsung berhenti. Tapi bagaimana kalau kita bisa menangkap exception itu sebelum membuat program kita crash? Bagaimana kalau kita bisa merespons error tersebut, misalnya dengan meminta pengguna mencoba lagi, atau memakai nilai default, atau mencatat masalahnya dan melanjutkan? Itulah tepatnya yang memungkinkan kita lakukan dengan penanganan exception.
25.1) Menggunakan Blok try dan except
25.1.1) Struktur Dasar try dan except
Sebuah blok try-except adalah cara Python untuk mengatakan "coba lakukan ini, dan jika exception terjadi, lakukan ini sebagai gantinya." Struktur dasarnya terlihat seperti ini:
try:
# Kode yang mungkin me-raise exception
risky_operation()
except:
# Kode yang berjalan jika ADA exception apa pun
print("Something went wrong!")Blok try berisi kode yang mungkin me-raise exception. Jika sebuah exception terjadi di mana pun di dalam blok try, Python langsung berhenti mengeksekusi blok try dan melompat ke blok except. Jika tidak ada exception yang terjadi, blok except dilewati sepenuhnya.
Mari lihat contoh konkret. Ingat dari Bab 24 bahwa mencoba mengonversi string yang tidak valid menjadi integer akan me-raise ValueError:
# Tanpa penanganan exception - program crash
user_input = "hello"
number = int(user_input) # ValueError: invalid literal for int() with base 10: 'hello'
print("This line never executes")Sekarang mari tangani exception ini dengan elegan:
# Dengan penanganan exception - program lanjut
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except:
print("That's not a valid number!")
number = 0 # Gunakan nilai default
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0Programnya tidak crash! Ketika int(user_input) me-raise ValueError, Python melompat ke blok except, mencetak pesan error kita, menetapkan nilai default, lalu melanjutkan dengan sisa program.
Berikut yang terjadi langkah demi langkah:
Memahami "Lompatan" - Apa yang Sebenarnya Terjadi
Saat kita mengatakan Python "melompat" ke blok except, maksudnya adalah Python meninggalkan eksekusi berurutan yang normal. Ini adalah perubahan mendasar pada bagaimana program kamu mengalir—bukan sekadar percabangan sederhana seperti statement if. Mari lihat ini secara detail dengan contoh konkret:
# Mengamati alur eksekusi dengan exception
print("1. Starting program")
try:
print("2. Entered try block")
number = int("hello") # Exception terjadi DI SINI
print("3. After conversion") # Baris ini TIDAK PERNAH dieksekusi
result = number * 2 # Baris ini TIDAK PERNAH dieksekusi
print("4. After calculation") # Baris ini TIDAK PERNAH dieksekusi
except ValueError:
print("5. In except block - handling the error")
print("6. After try-except block")Output:
1. Starting program
2. Entered try block
5. In except block - handling the error
6. After try-except blockPerhatikan bahwa baris 3 dan 4 tidak pernah dieksekusi! Saat int("hello") me-raise ValueError, Python:
- Berhenti mengeksekusi blok try seketika—tepat di baris tempat exception terjadi
- Mencari klausa except yang cocok yang bisa menangani tipe exception ini
- Melompat ke blok except tersebut, melewati semua sisa kode di blok try
- Melanjutkan setelah struktur try-except begitu blok except selesai
Ini pada dasarnya berbeda dari alur program normal. Dalam eksekusi normal, Python menjalankan tiap baris secara berurutan. Dengan exception, Python meninggalkan jalur saat ini dan mengambil rute yang benar-benar berbeda. Tanpa penanganan exception, program akan crash pada baris 2 dan berhenti. Dengan penanganan exception, program pulih dan lanjut.
Mengapa Ini Penting:
Memahami perilaku "lompatan" ini sangat krusial karena:
- Kode apa pun setelah exception di blok try akan dilewati — kamu tidak bisa mengasumsikan baris-baris berikutnya di blok try sempat dieksekusi
- Variabel mungkin belum terinisialisasi jika exception terjadi sebelum assignment-nya
- Kamu perlu merencanakan kondisi (state) program saat blok except berjalan
25.1.2) Menangani Input Pengguna dengan Aman
Salah satu penggunaan paling umum dari penanganan exception adalah memvalidasi input pengguna. Pengguna bisa mengetik apa saja, dan kita perlu menangani input yang tidak valid dengan elegan. Berikut contoh praktis program yang meminta umur pengguna:
# Input umur yang aman dengan penanganan exception
print("Please enter your age:")
user_input = input()
try:
age = int(user_input)
print(f"You are {age} years old.")
# Hitung tahun lahir (asumsikan tahun saat ini 2024)
birth_year = 2024 - age
print(f"You were born around {birth_year}.")
except:
print("Invalid input! Age must be a number.")
print("Using default age of 0.")
age = 0Jika pengguna memasukkan "25", output-nya adalah:
Please enter your age:
25
You are 25 years old.
You were born around 1999.Jika pengguna memasukkan "twenty-five", output-nya adalah:
Please enter your age:
twenty-five
Invalid input! Age must be a number.
Using default age of 0.Perhatikan bagaimana program menangani error dengan elegan alih-alih crash dengan traceback. Ini jauh lebih baik untuk pengalaman pengguna.
25.1.3) Menangani Banyak Operasi dalam Blok try
Kamu bisa menaruh beberapa operasi dalam satu blok try. Jika salah satunya me-raise exception, Python langsung melompat ke blok except. Mari mulai dengan contoh sederhana:
# Dua operasi di blok try
print("Enter a number:")
user_input = input()
try:
number = int(user_input) # Operasi pertama - bisa me-raise ValueError
result = 100 / number # Operasi kedua - bisa me-raise ZeroDivisionError
print(f"100 / {number} = {result}")
except:
print("Something went wrong!")Jika pengguna memasukkan "hello", exception terjadi pada operasi pertama (konversi). Jika pengguna memasukkan "0", exception terjadi pada operasi kedua (pembagian). Bagaimanapun, satu blok except kita menangkapnya.
Sekarang mari perluas ini menjadi tiga operasi:
# Banyak operasi di blok try
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input) # Bisa me-raise ValueError
denominator = int(denominator_input) # Bisa me-raise ValueError
result = numerator / denominator # Bisa me-raise ZeroDivisionError
print(f"{numerator} / {denominator} = {result}")
except:
print("Something went wrong with the calculation!")
print("Make sure you enter valid numbers and don't divide by zero.")Jika pengguna memasukkan "10" dan "2":
Enter two numbers to divide:
Numerator: 10
Denominator: 2
10 / 2 = 5.0Jika pengguna memasukkan "10" dan "zero":
Enter two numbers to divide:
Numerator: 10
Denominator: zero
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.Jika pengguna memasukkan "10" dan "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.Dalam contoh ini, tiga hal berbeda bisa salah: konversi pembilang bisa gagal, konversi penyebut bisa gagal, atau pembagian bisa gagal (jika penyebut nol). Satu blok except kita menangkap semua kasus ini. Namun, pendekatan ini punya keterbatasan: kita tidak bisa tahu error spesifik mana yang terjadi. Kita akan membahas ini pada bagian berikutnya.
25.1.4) Masalah dengan Klausa except Kosong (Bare except)
Menggunakan except: tanpa menyebutkan tipe exception disebut klausa bare except. Meskipun menangkap semua exception, ini sering kali terlalu luas dan bisa menyembunyikan masalah tak terduga. Pertimbangkan contoh ini:
# Bare except menangkap SEMUANYA - bahkan hal yang tidak kita duga
numbers = [10, 20, 30]
try:
index = 5 # Kita mengharapkan IndexError jika index di luar rentang
value = numbers[index]
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Ini terlihat masuk akal—kita mencoba mengakses elemen list yang mungkin tidak ada. Tapi bagaimana kalau ada typo di kode kita?
# Bagaimana jika ada typo di kode kita?
numbers = [10, 20, 30]
try:
index = 2
value = numbrs[index] # Typo: 'numbrs' alih-alih 'numbers'
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Output:
Could not access the list element.Bare except menangkap NameError dari typo dan mencetak "Could not access the list element"—memberi kita informasi yang salah tentang apa yang keliru! Kita mengira index di luar rentang, padahal sebenarnya ada typo pada nama variabel.
Bare except juga menangkap KeyboardInterrupt (saat pengguna menekan Ctrl+C) dan SystemExit (saat program mencoba keluar), yang biasanya tidak seharusnya ditangkap. Karena alasan ini, lebih baik menangkap exception yang spesifik, yang akan kita pelajari berikutnya.
25.2) Menangkap Exception Spesifik
25.2.1) Menentukan Tipe Exception
Alih-alih menangkap semua exception dengan bare except, kita bisa menentukan tipe exception mana yang ingin kita tangani. Ini membuat kode kita lebih presisi dan membantu kita merespons secara tepat untuk error yang berbeda:
# Menangkap tipe exception spesifik
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except ValueError:
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0Sekarang klausa except kita hanya menangkap exception ValueError. Jika tipe exception yang berbeda terjadi (seperti NameError dari typo), itu tidak akan tertangkap, dan kita akan melihat traceback lengkap—yang sebenarnya membantu untuk debugging!
Sintaksnya adalah: except ExceptionType: di mana ExceptionType adalah nama kelas exception yang ingin kamu tangkap (seperti ValueError, TypeError, ZeroDivisionError, dll.).
Kesalahan Umum: Menangkap Tipe Exception yang Salah
Apa yang terjadi jika kamu menentukan tipe exception yang tidak cocok dengan yang sebenarnya terjadi? Mari lihat:
# Menangkap tipe exception yang salah
user_input = "hello"
try:
number = int(user_input) # Ini me-raise ValueError
print(f"You entered: {number}")
except TypeError: # Tapi kita menangkap TypeError!
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
Traceback (most recent call last):
File "example.py", line 4, in <module>
number = int(user_input)
ValueError: invalid literal for int() with base 10: 'hello'Programnya crash! Kenapa? Karena int("hello") me-raise ValueError, tetapi klausa except kita hanya menangkap TypeError. Karena tidak ada klausa except yang cocok, exception tidak tertangkap, dan program berhenti.
Ini sebenarnya membantu selama pengembangan—kalau kamu menangkap tipe exception yang salah, kamu akan melihat traceback lengkap dan menyadari kesalahanmu. Ini salah satu alasan mengapa menangkap exception spesifik lebih baik daripada menggunakan bare except.
Cara menghindari kesalahan ini:
- Baca traceback untuk melihat tipe exception apa yang benar-benar terjadi
- Gunakan tipe exception spesifik itu di klausa except kamu
- Jika kamu tidak yakin, jalankan kode dan biarkan crash—traceback akan memberi tahu kamu!
25.2.2) Menangani Exception Berbeda dengan Cara Berbeda
Kamu bisa punya beberapa klausa except untuk menangani tipe exception yang berbeda dengan cara yang berbeda. Ini sangat berguna ketika error yang berbeda membutuhkan respons yang berbeda:
# Penanganan berbeda untuk exception yang berbeda
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input)
denominator = int(denominator_input)
result = numerator / denominator
print(f"{numerator} / {denominator} = {result}")
except ValueError:
print("Error: Both inputs must be valid integers.")
print("You entered something that isn't a number.")
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
print("The denominator must be a non-zero number.")Jika pengguna memasukkan "10" dan "abc":
Enter two numbers to divide:
Numerator: 10
Denominator: abc
Error: Both inputs must be valid integers.
You entered something that isn't a number.Jika pengguna memasukkan "10" dan "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Error: Cannot divide by zero.
The denominator must be a non-zero number.Python memeriksa setiap klausa except secara berurutan. Saat exception terjadi, Python menemukan klausa except pertama yang cocok dengan tipe exception tersebut dan mengeksekusi blok itu. Klausa except lainnya dilewati.
25.2.3) Menangkap Banyak Tipe Exception dalam Satu Klausa
Terkadang kamu ingin menangani beberapa tipe exception yang berbeda dengan cara yang sama. Alih-alih menulis beberapa blok except yang identik, kamu bisa menangkap banyak tipe exception dalam satu klausa dengan menaruhnya dalam tanda kurung sebagai tuple:
# Menangkap beberapa tipe exception sekaligus
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"100 divided by {number} is {result}")
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero.")
print("Please enter a non-zero number.")Jika pengguna memasukkan "hello":
Enter a number:
hello
Invalid input or division by zero.
Please enter a non-zero number.Jika pengguna memasukkan "0":
Enter a number:
0
Invalid input or division by zero.
Please enter a non-zero number.Baik ValueError (dari konversi yang tidak valid) maupun ZeroDivisionError (dari pembagian nol) ditangani oleh klausa except yang sama. Ini berguna ketika error yang berbeda seharusnya memicu respons yang sama.
25.2.4) Mengakses Informasi Exception
Terkadang kamu perlu tahu detail lebih lanjut tentang exception yang terjadi. Kamu bisa menangkap objek exception menggunakan keyword as. Tapi pertama, mari pahami apa itu objek exception.
Apa Itu Objek Exception?
Saat Python me-raise exception, Python tidak hanya memberi sinyal bahwa sesuatu salah—Python membuat sebuah objek yang berisi informasi tentang error. Objek exception ini seperti laporan error yang detail yang mencakup:
- Pesan error: Deskripsi tentang apa yang salah
- Tipe exception: Jenis error yang terjadi (ValueError, TypeError, dll.)
- Atribut tambahan: Informasi spesifik tergantung tipe exception
Anggap objek exception sebagai wadah yang menampung semua informasi tentang sebuah error. Seperti objek list berisi item dan punya method seperti append(), objek exception berisi informasi error dan punya atribut yang bisa kamu akses.
Saat kamu menulis except ValueError as error:, kamu memberi tahu Python: "Jika ValueError terjadi, buat variabel bernama error dan masukkan objek exception ke dalamnya supaya aku bisa memeriksanya."
Mari eksplor isi sebuah objek exception:
# Memeriksa isi objek exception
try:
number = int("hello")
except ValueError as error:
print("Exception caught! Let's examine it:")
print(f"Type: {type(error)}")
print(f"String representation: {error}")
print(f"Args tuple: {error.args}")Output:
Exception caught! Let's examine it:
Type: <class 'ValueError'>
String representation: invalid literal for int() with base 10: 'hello'
Args tuple: ("invalid literal for int() with base 10: 'hello'",)Objek exception memiliki:
- Sebuah type (kelas ValueError) — ini memberitahumu jenis error yang terjadi
- Sebuah representasi string (pesan error) — ini yang kamu lihat di traceback
- Atribut args (tuple yang berisi pesan dan argumen lain) — ini memberikan akses terstruktur ke detail error
Mengapa Ini Penting:
Tipe exception yang berbeda punya atribut yang berbeda yang menyediakan informasi spesifik. Memahami struktur objek exception membantumu mengekstrak informasi berguna untuk debugging atau umpan balik ke pengguna:
# Exception yang berbeda punya atribut yang berbeda
numbers = [10, 20, 30]
try:
value = numbers[10]
except IndexError as error:
print(f"IndexError message: {error}")
print(f"Exception args: {error.args}")
# Sekarang coba dengan dictionary
grades = {"Alice": 95}
try:
grade = grades["Bob"]
except KeyError as error:
print(f"KeyError message: {error}")
print(f"Missing key: {error.args[0]}")Output:
IndexError message: list index out of range
Exception args: ('list index out of range',)
KeyError message: 'Bob'
Missing key: BobPerhatikan bagaimana KeyError menyertakan key sebenarnya yang hilang dalam pesannya. Tipe exception yang berbeda menyediakan informasi berguna yang berbeda yang bisa kamu akses lewat objek exception.
25.3) Menggunakan else dan finally dengan Blok try
25.3.1) Klausa else: Kode yang Berjalan Hanya Saat Sukses
Klausa else dalam blok try-except berjalan hanya jika tidak ada exception yang terjadi di blok try. Ini berguna untuk kode yang hanya boleh dieksekusi ketika operasi berisiko berhasil:
# Menggunakan else untuk kode yang hanya berjalan saat sukses
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
except ValueError:
print("That's not a valid number!")
else:
# Ini hanya berjalan jika int(user_input) berhasil
print(f"Successfully converted: {number}")
squared = number ** 2
print(f"The square of {number} is {squared}")Jika pengguna memasukkan "5":
Enter a number:
5
Successfully converted: 5
The square of 5 is 25Jika pengguna memasukkan "hello":
Enter a number:
hello
That's not a valid number!Kenapa memakai else alih-alih menaruh kode itu di akhir blok try saja? Ada dua alasan penting:
- Kejelasan: Klausa
elsemembuat jelas bahwa kode ini hanya berjalan saat sukses - Cakupan exception: Exception yang di-raise di klausa
elsetidak ditangkap oleh klausaexceptsebelumnya
Berikut contoh yang menunjukkan kenapa poin kedua itu penting:
# Mendemonstrasikan mengapa else berguna untuk cakupan exception
try:
number_1 = int(input("Enter a number_1: "))
except ValueError:
print("Invalid input!")
else:
# Jika error terjadi di sini, itu tidak akan tertangkap oleh except di atas
# Ini membantu membedakan antara error input dan error pemrosesan
number_2 = int(input("Enter a number_2: ")) # Bisa me-raise ValueErrorJika kita menaruh number_2 = int(input(...)) di blok try bersama number_1, ValueError apa pun dari salah satu input akan ditangkap oleh klausa except ValueError yang sama. Ini membuat kita tidak bisa membedakan input mana yang menyebabkan masalah.
Dengan menaruh number_2 = int(input(...)) di blok else, kita memisahkan penanganan error. Klausa except hanya menangkap error dari number_1, sementara error dari number_2 akan me-raise exception yang tidak tertangkap dengan traceback lengkap—membuat jelas bahwa input kedua yang gagal, bukan yang pertama.
25.3.2) Klausa finally: Kode yang Selalu Berjalan
Klausa finally berisi kode yang berjalan apa pun yang terjadi—baik exception terjadi atau tidak, baik tertangkap atau tidak. Ini penting untuk operasi cleanup yang harus selalu terjadi:
# Menggunakan finally untuk cleanup
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"Result: {result}")
except ValueError:
print("Invalid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
finally:
print("Calculation attempt completed.")Jika pengguna memasukkan "5":
Enter a number:
5
Result: 20.0
Calculation attempt completed.Jika pengguna memasukkan "hello":
Enter a number:
hello
Invalid number!
Calculation attempt completed.Jika pengguna memasukkan "0":
Enter a number:
0
Cannot divide by zero!
Calculation attempt completed.Blok finally berjalan di ketiga kasus! Ini adalah perilaku kunci dari finally: ia selalu dieksekusi, terlepas dari apa yang terjadi di blok try.
25.3.3) Menggabungkan try, except, else, dan finally
Kamu bisa menggunakan keempat klausa sekaligus untuk membuat penanganan exception yang komprehensif:
# Struktur penanganan exception lengkap
print("Enter a number to calculate its reciprocal:")
user_input = input()
try:
# Operasi yang berisiko
number = int(user_input)
reciprocal = 1 / number
except ValueError:
# Tangani error konversi
print("Error: Input must be a valid integer.")
except ZeroDivisionError:
# Tangani pembagian dengan nol
print("Error: Cannot calculate reciprocal of zero.")
else:
# Kode yang hanya berjalan saat sukses
print(f"The reciprocal of {number} is {reciprocal}")
print(f"Verification: {number} × {reciprocal} = {number * reciprocal}")
finally:
# Kode cleanup yang selalu berjalan
print("Reciprocal calculation completed.")Jika pengguna memasukkan "4":
Enter a number to calculate its reciprocal:
4
The reciprocal of 4 is 0.25
Verification: 4 × 0.25 = 1.0
Reciprocal calculation completed.Jika pengguna memasukkan "hello":
Enter a number to calculate its reciprocal:
hello
Error: Input must be a valid integer.
Reciprocal calculation completed.Jika pengguna memasukkan "0":
Enter a number to calculate its reciprocal:
0
Error: Cannot calculate reciprocal of zero.
Reciprocal calculation completed.Alur eksekusinya adalah:
- Blok
tryselalu dieksekusi terlebih dahulu - Jika exception terjadi, blok
exceptyang cocok dieksekusi - Jika tidak ada exception yang terjadi, blok
elsedieksekusi (jika ada) - Blok
finallyselalu dieksekusi terakhir, terlepas dari apa yang terjadi
25.4) Me-raise Exception Secara Sengaja dengan raise
25.4.1) Mengapa Me-raise Exception?
Sejauh ini, kita menangkap exception yang Python raise secara otomatis. Tapi kadang-kadang kamu perlu me-raise exception secara sengaja di kode kamu sendiri. Ini berguna ketika:
- Kamu mendeteksi situasi tidak valid yang tidak bisa ditangani kode kamu
- Kamu ingin menegakkan aturan atau batasan
- Kamu ingin memberi sinyal error ke kode yang memanggil fungsi kamu
Me-raise exception adalah cara Python untuk mengatakan "Aku tidak bisa lanjut—ada yang salah, dan siapa pun yang memanggilku perlu menanganinya."
Sintaksnya sederhana: raise ExceptionType("error message")
Berikut contoh dasar:
# Me-raise exception secara sengaja
age = -5
if age < 0:
raise ValueError("Age cannot be negative!")
print(f"Age: {age}") # Baris ini tidak pernah dieksekusiOutput:
Traceback (most recent call last):
File "example.py", line 5, in <module>
raise ValueError("Age cannot be negative!")
ValueError: Age cannot be negative!Saat Python menemui raise, Python langsung membuat exception dan mulai mencari blok except untuk menanganinya. Jika tidak ada, program berhenti dengan traceback.
25.4.2) Me-raise Exception di Fungsi
Me-raise exception sangat berguna di fungsi(function) untuk memvalidasi input dan menegakkan batasan:
# Fungsi yang memvalidasi input dengan me-raise exception
def calculate_discount(price, discount_percent):
"""Calculate discounted price.
Args:
price: Original price (must be positive)
discount_percent: Discount percentage (must be 0-100)
Returns:
Discounted price
Raises:
ValueError: If inputs are invalid
"""
if price < 0:
raise ValueError("Price cannot be negative!")
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Discount must be between 0 and 100!")
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Menggunakan fungsi
try:
final_price = calculate_discount(100, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Final price: $80.0Sekarang mari coba dengan input yang tidak valid:
# Harga tidak valid
try:
final_price = calculate_discount(-50, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Price cannot be negative!# Diskon tidak valid
try:
final_price = calculate_discount(100, 150)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Discount must be between 0 and 100!Dengan me-raise exception, fungsi tersebut mengomunikasikan dengan jelas apa yang salah. Kode pemanggil kemudian bisa memutuskan cara menangani error—mungkin dengan meminta pengguna input baru, menggunakan nilai default, atau mencatat error.
25.4.3) Memilih Tipe Exception yang Tepat
Python memiliki banyak tipe exception bawaan, dan memilih yang tepat membuat kode kamu lebih jelas. Berikut exception yang paling sering dipakai untuk validasi:
- ValueError: Gunakan saat nilainya bertipe benar tetapi nilainya tidak sesuai (misalnya umur negatif, persentase tidak valid)
- TypeError: Gunakan saat nilainya bertipe benar-benar salah (misalnya string alih-alih angka)
- KeyError: Gunakan saat key dictionary tidak ada
- IndexError: Gunakan saat index sequence di luar rentang
Berikut contoh yang menunjukkan tipe exception yang berbeda:
# Menggunakan tipe exception yang sesuai
def get_student_grade(grades, student_name):
"""Get a student's grade from the grades dictionary.
Args:
grades: Dictionary mapping student names to grades
student_name: Name of the student
Returns:
The student's grade
Raises:
TypeError: If grades is not a dictionary
KeyError: If student_name is not in grades
ValueError: If the grade is invalid
"""
if not isinstance(grades, dict):
raise TypeError("Grades must be a dictionary!")
if student_name not in grades:
raise KeyError(f"Student '{student_name}' not found!")
grade = grades[student_name]
if not (0 <= grade <= 100):
raise ValueError(f"Invalid grade: {grade} (must be 0-100)")
return grade
# Uji dengan data valid
grades = {"Alice": 95, "Bob": 87, "Carol": 92}
try:
grade = get_student_grade(grades, "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Alice's grade: 95# Uji dengan siswa yang tidak ada
try:
grade = get_student_grade(grades, "David")
print(f"David's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Student 'David' not found!# Uji dengan tipe yang salah
try:
grade = get_student_grade("not a dict", "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Grades must be a dictionary!Menggunakan tipe exception yang sesuai membantu programmer lain (dan dirimu di masa depan) memahami jenis error yang terjadi.
25.4.4) Me-raise Kembali Exception (Re-raising)
Terkadang kamu ingin menangkap exception, melakukan sesuatu (seperti logging), lalu membiarkan exception itu terus menerus (propagate). Kamu bisa melakukan ini dengan menggunakan raise tanpa argumen apa pun di dalam blok except:
# Me-raise kembali exception setelah logging
def divide_numbers(a, b):
"""Divide two numbers with error logging."""
try:
result = a / b
return result
except ZeroDivisionError:
print("ERROR LOG: Division by zero attempted")
print(f" Numerator: {a}, Denominator: {b}")
raise # Me-raise kembali exception yang sama
# Menggunakan fungsi
try:
result = divide_numbers(10, 0)
print(f"Result: {result}")
except ZeroDivisionError:
print("Cannot divide by zero!")Output:
ERROR LOG: Division by zero attempted
Numerator: 10, Denominator: 0
Cannot divide by zero!Statement raise tanpa argumen me-raise kembali exception yang baru saja ditangkap. Ini berguna ketika kamu ingin:
- Mencatat atau merekam error
- Melakukan cleanup
- Membiarkan error diteruskan ke pemanggil
25.4.5) Me-raise Exception dari Exception Lain
Terkadang kamu ingin me-raise exception baru sambil menangani exception lain, sambil mempertahankan konteks error aslinya. Python 3 menyediakan sintaks raise ... from ... untuk ini:
# Me-raise exception baru dari exception yang sudah ada
def load_config(config_dict, key):
"""Load configuration value from dictionary."""
try:
config_value = config_dict[key]
# Coba parse sebagai integer
parsed_value = int(config_value)
return parsed_value
except KeyError as error:
raise RuntimeError(f"Configuration key missing: {key}") from error
except ValueError as error:
raise RuntimeError(f"Invalid configuration format for {key}") from error
# Menggunakan fungsi
config = {"timeout": "30", "retries": "5"}
try:
value = load_config(config, "timeout")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Config value: 30Jika key tidak ada:
try:
value = load_config(config, "missing_key")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Configuration error: Configuration key missing: missing_key
Original cause: 'missing_key'Keyword from menghubungkan exception baru dengan exception asli. Ini membuat rantai exception yang membantu debugging—kamu bisa melihat apa yang salah di level tinggi (configuration error) dan apa penyebab dasarnya (key tidak ditemukan).
Penanganan exception adalah salah satu alat terpenting untuk menulis program yang andal. Dengan menggunakan blok try-except, kamu bisa mengantisipasi masalah, menanganinya dengan elegan, dan memberikan pengalaman yang lebih baik untuk pengguna. Ingat:
- Gunakan
try-exceptuntuk menangani error yang diperkirakan terjadi dengan elegan - Tangkap tipe exception yang spesifik daripada menggunakan bare
except - Gunakan
elseuntuk kode yang seharusnya hanya berjalan saat sukses - Gunakan
finallyuntuk kode cleanup yang harus selalu berjalan - Me-raise exception di kode kamu sendiri untuk memberi sinyal adanya masalah
- Pilih tipe exception yang sesuai agar error jelas
- Berikan pesan error yang membantu yang menjelaskan apa yang salah
Di bab berikutnya, kita akan mempelajari teknik pemrograman defensif yang menggabungkan penanganan exception dengan validasi input dan strategi lain agar program kita lebih tangguh lagi.