Python 3 — Exception Handling
Errors in Python are called exceptions. Instead of crashing your program when something goes wrong, Python lets you catch exceptions and handle them gracefully. This makes your programs robust, user-friendly, and production-ready.
1 Common Built-in Exceptions
| Exception | Cause | Example |
|---|---|---|
ValueError | Wrong value type/format | int("hello") |
TypeError | Wrong data type | "a" + 5 |
ZeroDivisionError | Division by zero | 10 / 0 |
IndexError | List index out of range | [1,2][5] |
KeyError | Dictionary key missing | d["x"] |
FileNotFoundError | File doesn't exist | open("x.txt") |
AttributeError | Object has no attribute | "hi".upper2() |
NameError | Variable not defined | print(x) |
ImportError | Module not found | import xyz |
2 Basic try-except
Python 3 — try-except▶ Run Code
# Without error handling — program crashes!
# num = int("hello") # ValueError: invalid literal
# With error handling — program continues
try:
num = int("hello")
print(f"Number: {num}")
except ValueError as e:
print(f"❌ ValueError: {e}")
print("Please enter a valid number.")
print("Program continues running...")
# ZeroDivisionError
try:
result = 100 / 0
print(result)
except ZeroDivisionError:
print("❌ Cannot divide by zero!")
# Multiple except blocks
try:
data = [1, 2, 3]
value = int("abc")
item = data[10]
except ValueError:
print("❌ Invalid number format")
except IndexError:
print("❌ Index out of range")
except Exception as e:
print(f"❌ Unexpected error: {e}")
3 try-except-else-finally
Python 3 — Full try Block▶ Run Code
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("❌ Division by zero!")
return None
except TypeError:
print("❌ Both values must be numbers!")
return None
else:
# Runs ONLY when no exception occurred
print(f"✅ Success! {a} / {b} = {result}")
return result
finally:
# ALWAYS runs, even if there's an exception
print("--- Division attempt complete ---")
divide(10, 2) # Succeeds
print()
divide(10, 0) # ZeroDivisionError
print()
divide("a", 2) # TypeError
4 Raising Exceptions
Python 3 — raise▶ Run Code
def set_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer!")
if age < 0 or age > 150:
raise ValueError(f"Invalid age: {age}. Must be 0-150.")
print(f"Age set to: {age}")
# Test valid age
set_age(25)
# Test invalid types
try:
set_age("twenty")
except TypeError as e:
print(f"TypeError: {e}")
# Test invalid range
try:
set_age(-5)
except ValueError as e:
print(f"ValueError: {e}")
# Re-raise an exception after logging
def process_file(filename):
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
print(f"Log: File '{filename}' not found")
raise # Re-raise same exception
5 Custom Exception Classes
Python 3 — Custom Exceptions▶ Run Code
# Create custom exceptions by inheriting from Exception
class InsufficientFundsError(Exception):
"""Raised when a bank account has insufficient balance."""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
self.message = f"Insufficient funds! Balance: ₹{balance}, Requested: ₹{amount}"
super().__init__(self.message)
class InvalidAccountError(Exception):
"""Raised for invalid account numbers."""
pass
# Bank account class using custom exceptions
class BankAccount:
def __init__(self, account_id, balance):
self.account_id = account_id
self.balance = balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive!")
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
print(f"✅ Withdrew ₹{amount}. New balance: ₹{self.balance}")
account = BankAccount("ACC001", 5000)
try:
account.withdraw(3000)
account.withdraw(3000) # Will fail
except InsufficientFundsError as e:
print(f"❌ {e}")
except ValueError as e:
print(f"❌ {e}")
6 Exception Chaining
Python 3 — Exception Chaining▶ Run Code
# raise ... from ... — attach context to exceptions
def load_config(filename):
try:
with open(filename) as f:
import json
return json.load(f)
except FileNotFoundError as e:
raise RuntimeError(f"Config file missing: {filename}") from e
except json.JSONDecodeError as e:
raise ValueError(f"Config file is invalid JSON") from e
try:
config = load_config("missing_config.json")
except RuntimeError as e:
print(f"RuntimeError: {e}")
print(f"Caused by: {e.__cause__}")
7 Context Managers & with Statement
Python 3 — with Statement▶ Run Code
# 'with' ensures resources are always cleaned up
# Even if an exception occurs inside the block!
# File handling with 'with'
with open("safe.txt", "w") as f:
f.write("Safe file handling!")
# File is automatically closed here, even if exception occurs
# Create a custom context manager using contextlib
from contextlib import contextmanager
@contextmanager
def timer():
import time
start = time.time()
try:
yield # Control passes to the 'with' block
finally:
end = time.time()
print(f"⏱️ Execution time: {end - start:.4f}s")
with timer():
result = sum(range(1_000_000))
print(f"Sum: {result}")
8 Coding Challenge
Build a robust user registration system:
- Create custom exceptions:
UsernameTakenError,WeakPasswordError,InvalidEmailError - Write a
register_user(username, email, password)function that raises these exceptions for: taken usernames, passwords shorter than 8 chars, emails without "@", duplicate registrations - Use a dictionary to store registered users
- Test with: a valid registration, duplicate username, weak password, and invalid email
- Use try-except-else to print success or appropriate error messages