🕯️ Magic Note
Exceptions are not errors. They are events. Python raises an exception when something exceptional happens—something that does not fit the normal flow of the program. Exceptions can be caught and handled. This is the “Easier to ask for forgiveness than permission” (EAFP) style, which is central to Python’s design philosophy.
Python
try:
number = int(input(“Enter a number: “))
print(f”You entered {number}”)
except ValueError:
print(“That was not a valid number!”)
# If user enters “hello”, the program does not crash.
Python
try:
num = int(input(“Enter a number: “))
result = 100 / num
print(f”100 / {num} = {result}”)
except ValueError:
print(“Error: Please enter a valid number”)
except ZeroDivisionError:
print(“Error: Cannot divide by zero”)
🕯️ Magic Note
When multiple except blocks are present, Python checks them in order. The first matching exception block runs. This is why you should put more specific exceptions before more general ones.
Python
try:
value = int(input(“Enter a number: “))
result = 100 / value
print(f”Result: {result}”)
except (ValueError, ZeroDivisionError) as e:
print(f”Error: {e}”)
Python
try:
with open(“nonexistent.txt”, “r”) as f:
content = f.read()
except FileNotFoundError as e:
print(f”Error type: {type(e).__name__}”)
print(f”Error message: {e}”)
print(f”Errno: {e.errno}”)
print(f”Filename: {e.filename}”)
Python
# Dangerous: catches everything
try:
risky_operation()
except: # Bare except – not recommended
print(“Something went wrong”)
# Better: catches all normal exceptions
try:
risky_operation()
except Exception as e:
print(f”Something went wrong: {e}”)
Python
try:
file = open(“data.txt”, “r”)
except FileNotFoundError:
print(“File not found. Creating new file.”)
file = open(“data.txt”, “w”)
else:
# This runs only if no exception occurred
content = file.read()
print(f”File contained: {content}”)
finally:
file.close()
🕯️ Magic Note
The else clause is useful for code that should not be in the try block (where it might catch exceptions you did not intend) but should only run if the try succeeded.
Python
file = None
try:
file = open(“important.txt”, “r”)
content = file.read()
print(content)
except FileNotFoundError:
print(“File not found”)
except PermissionError:
print(“Permission denied”)
finally:
# This ALWAYS runs
if file:
file.close()
print(“File closed”)
Python
def divide_numbers(a, b):
try:
result = a / b
except ZeroDivisionError:
print(“Error: Division by zero”)
return None
except TypeError:
print(“Error: Both arguments must be numbers”)
return None
else:
print(“Division successful”)
return result
finally:
print(“Operation completed”)
print(divide_numbers(10, 2))
print(divide_numbers(10, 0))
print(divide_numbers(10, “2”))
Python
def set_age(age):
if age < 0:
raise ValueError(“Age cannot be negative”)
if age > 150:
raise ValueError(“Age is unreasonably high”)
self.age = age
# Reraising an exception
try:
set_age(-5)
except ValueError as e:
print(f”Caught: {e}”)
raise # Reraises the same exception
🕯️ Magic Note
You can create your own exception classes by inheriting from Exception. This is useful for libraries where you want users to catch specific errors from your code.
Python
# Creating custom exceptions
class InsufficientFundsError(Exception):
pass
class AccountNotFoundError(Exception):
def __init__(self, account_id):
self.account_id = account_id
super().__init__(f”Account {account_id} not found”)
def withdraw(account, amount):
if account.balance < amount:
raise InsufficientFundsError(f”Need {amount – account.balance} more”)
account.balance -= amount
| Exception | When It Occurs |
|---|---|
| ValueError | Function receives argument of correct type but inappropriate value |
| TypeError | Operation applied to object of wrong type |
| IndexError | Sequence index out of range |
| KeyError | Dictionary key not found |
| FileNotFoundError | File or directory does not exist |
| ZeroDivisionError | Division or modulo by zero |
| ImportError | Import statement fails to find module |
| AttributeError | Object does not have requested attribute |
| NameError | Variable name not found |
| KeyboardInterrupt | User presses Ctrl+C |
| StopIteration | Next() called on iterator with no items |
Python
def get_integer(prompt, min_value=None, max_value=None):
“””Get an integer from the user with optional range validation.”””
while True:
try:
value = int(input(prompt))
if min_value is not None and value < min_value:
print(f”Value must be at least {min_value}”)
continue
if max_value is not None and value > max_value:
print(f”Value must be at most {max_value}”)
continue
return value
except ValueError:
print(“Invalid input. Please enter a valid integer.”)
# Usage
age = get_integer(“Enter your age: “, min_value=0, max_value=150)
score = get_integer(“Enter score (0-100): “, 0, 100)
Python
def safe_read_file(filename):
“””Read a file safely, returning content or None if error.”””
try:
with open(filename, “r”, encoding=”utf-8″) as f:
return f.read()
except FileNotFoundError:
print(f”Error: File ‘{filename}’ not found”)
return None
except PermissionError:
print(f”Error: Permission denied to read ‘{filename}'”)
return None
except UnicodeDecodeError as e:
print(f”Error: Cannot decode file ‘{filename}’: {e}”)
return None
except Exception as e:
print(f”Unexpected error reading ‘{filename}’: {e}”)
return None
content = safe_read_file(“config.txt”)
if content:
print(“File read successfully”)
Python
# LBYL (Look Before You Leap) – C-style
if “name” in user_data:
name = user_data[“name”]
else:
name = “Unknown”
# EAFP (Easier to Ask for Forgiveness than Permission) – Pythonic
try:
name = user_data[“name”]
except KeyError:
name = “Unknown”
# Even simpler with .get() for dictionaries
name = user_data.get(“name”, “Unknown”)
🕯️ Magic Note
EAFP is Pythonic because it avoids race conditions (the condition might change between check and action) and is often faster when the success case is common. However, use good judgment—sometimes LBYL is clearer.
- Using bare except: that catches too much (including Ctrl+C)
- Empty except block that silently ignores errors
- Catching exceptions too broadly (e.g., Exception when specific types are needed)
- Not cleaning up resources in finally or using with statements
- Raising generic Exception instead of specific subclasses
- Putting too much code in the try block (masking unexpected errors)
Python
# Bad: Empty except
try:
result = 10 / 0
except:
pass # Silent failure – very dangerous!
# Better: Log or handle appropriately
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f”Error: {e}”)
result = None
- What is the difference between except ValueError: and except:?
- When does the else clause in a try-except block execute?
- What is the purpose of the finally clause?
- Write a function that divides two numbers and handles division by zero gracefully.
- How do you create a custom exception?
- What does EAFP stand for and why is it Pythonic?
⚡ Whisper
Errors are not enemies. They are messengers. The exception says: “Something unexpected happened. Pay attention.” You can ignore the message, and the program will crash. Or you can listen. You can wrap the risky code in try and catch the exception with except. You can clean up with finally. You can succeed silently with else. And when you detect a problem yourself, you can raise your own exception with raise. This is not defensive programming. This is resilient programming. Your code does not pretend that errors never happen. It prepares for them. It handles them. It recovers. A program that never handles errors is a house of cards. A program that handles errors gracefully is a fortress. Build fortresses. Handle your exceptions. Your users will thank you. And your future self will too.