Intermediate
Exception Handling: try, except, finally, and Custom Exceptions
📂 Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20) · Python ProgrammingAn exception is an error detected during program execution that, left unhandled, crashes the program immediately. Exception handling lets you anticipate likely failure points and respond gracefully instead — keeping your program running, or at least failing in a controlled, informative way.
The Basic try-except Block
try:
result = 10 / 0
except ZeroDivisionError:
print("You can't divide by zero!")
# Output: You can't divide by zero!
# Without the try-except, this would crash the entire program
Catching Multiple Exception Types
try:
num = int(input("Enter a number: "))
result = 10 / num
except ValueError:
print("That was not a valid number")
except ZeroDivisionError:
print("You can't divide by zero!")
Catching Several Exceptions in One Block
try:
value = int("abc")
except (ValueError, TypeError):
print("Either a value error or a type error occurred")
Accessing the Exception Object
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"An error occurred: {e}")
# An error occurred: division by zero
The else Clause — Runs Only If No Exception Occurred
try:
num = int("42")
except ValueError:
print("Conversion failed")
else:
print(f"Conversion succeeded: {num}") # only runs if try succeeded
The finally Clause — Always Runs
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found")
finally:
print("This always runs, error or not")
# commonly used for cleanup, like closing a file or releasing a resource
The Complete try-except-else-finally Structure
try:
num = int(input("Enter a number: "))
result = 100 / num
except ValueError:
print("Please enter a valid integer")
except ZeroDivisionError:
print("Cannot divide by zero")
else:
print(f"Result: {result}")
finally:
print("Execution complete")
Raising an Exception Manually
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
print(f"Age set to {age}")
try:
set_age(-5)
except ValueError as e:
print(e) # Age cannot be negative
Creating a Custom Exception
For application-specific error conditions, defining your own exception class (inheriting from the built-in Exception) produces far more readable and meaningful error handling than reusing generic built-in exceptions everywhere.
class InsufficientBalanceError(Exception):
"""Raised when a withdrawal exceeds the available balance."""
pass
class Account:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientBalanceError(
f"Cannot withdraw {amount}; balance is only {self.balance}"
)
self.balance -= amount
acc = Account(500)
try:
acc.withdraw(1000)
except InsufficientBalanceError as e:
print(e)
# Cannot withdraw 1000; balance is only 500
Common Built-In Exception Types
| Exception | Raised When |
|---|---|
| ValueError | A value has the right type but an invalid value (e.g. int("abc")) |
| TypeError | An operation is applied to an object of an inappropriate type |
| ZeroDivisionError | Division or modulus by zero |
| FileNotFoundError | Attempting to open a file that does not exist |
| KeyError | Accessing a dictionary key that does not exist |
| IndexError | Accessing a list index that is out of range |
Interview tip: "Why create a custom exception instead of just raising a generic Exception?" — a custom exception class lets calling code catch your specific error type precisely (except InsufficientBalanceError) without accidentally also catching unrelated errors that happen to also be generic Exceptions, making error handling far more intentional and readable.