- Use raise to throw exceptions
- Create custom exception classes
- Know when to raise vs return errors
- Re-raise exceptions when needed
Raising Exceptions
So far, you've learned to catch errors. But what if YOUR code detects a problem? You need to signal to the rest of the program: "Stop! Something is wrong!" This is called raising (or throwing) an exception.
Think of it like being a referee in a game. When you see a foul, you blow the whistle and stop play. Raising an exception is your program's whistle!
The raise Statement
# Basic raise
raise ValueError("Age cannot be negative")
# With more context
age = -5
if age < 0:
raise ValueError(f"Age must be positive, got {age}")
When the Exception is Raised
What Happens When You Raise
def validate_age(age):
if age < 0:
raise ValueError("Negative!") ← Program stops here
return age ← This never runs if age < 0
try:
validate_age(-5)
except ValueError as e: ← Exception caught here
print(f"Error: {e}")
# If not caught, program crashes with traceback
Choosing the Right Exception
Use Python's built-in exceptions when appropriate:
| Exception | When to Use |
|---|---|
ValueError |
Wrong value (right type, wrong value) |
TypeError |
Wrong type |
KeyError |
Missing dictionary key |
IndexError |
Index out of range |
FileNotFoundError |
File doesn't exist |
PermissionError |
No permission |
RuntimeError |
General runtime error |
def divide(a, b):
if not isinstance(b, (int, float)):
raise TypeError(f"b must be a number, got {type(b)}")
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Custom Exceptions
Create your own exception types for specific errors:
# Simple custom exception
class InsufficientFundsError(Exception):
"""Raised when account has insufficient funds."""
pass
# Custom exception with extra data
class ValidationError(Exception):
"""Raised when validation fails."""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
Using Custom Exceptions
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"Cannot withdraw ${amount}. Balance: ${self.balance}"
)
self.balance -= amount
return amount
# Usage
account = BankAccount(100)
try:
account.withdraw(150)
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")
Re-raising Exceptions
Sometimes you want to catch an exception, do something, then let it continue:
def process_file(filename):
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
print(f"Logging: File {filename} not found")
raise # Re-raise the same exception
# The caller still needs to handle it
try:
data = process_file("missing.txt")
except FileNotFoundError:
print("Handling in caller")
Exception Chaining
Show what caused an exception:
def load_config(filename):
try:
with open(filename) as f:
return parse_config(f.read())
except FileNotFoundError as e:
raise ConfigError(f"Cannot load config") from e
Practical Examples
Example 1: Input Validation
def create_user(name, age, email):
"""Create a user with validation."""
# Validate name
if not name or not name.strip():
raise ValueError("Name cannot be empty")
# Validate age
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0 or age > 150:
raise ValueError(f"Age must be 0-150, got {age}")
# Validate email
if "@" not in email:
raise ValueError("Invalid email format")
return {"name": name.strip(), "age": age, "email": email}
# Usage
try:
user = create_user("Alice", 25, "alice@example.com")
print(f"Created: {user}")
except (ValueError, TypeError) as e:
print(f"Validation failed: {e}")
Example 2: API Response Handler
class APIError(Exception):
"""Base exception for API errors."""
pass
class NotFoundError(APIError):
"""Resource not found."""
pass
class AuthenticationError(APIError):
"""Authentication failed."""
pass
def handle_response(status_code, data):
"""Handle API response and raise appropriate errors."""
if status_code == 200:
return data
elif status_code == 401:
raise AuthenticationError("Invalid credentials")
elif status_code == 404:
raise NotFoundError("Resource not found")
else:
raise APIError(f"API error: {status_code}")
When to Raise vs Return
Raise Exception vs Return Value
USE EXCEPTIONS when:
• Something unexpected/exceptional happened
• The function cannot fulfill its contract
• The caller MUST handle it (can't be ignored)
RETURN VALUES when:
• The outcome is an expected possibility
• Finding "nothing" is a valid result
• The caller might reasonably ignore it
Example:
• find_user(id) → return None if not found (expected)
• get_user(id) → raise NotFoundError (must exist)
Key Takeaways
Remember These Points
raise ExceptionType("message")
Signals that something went wrong
Use appropriate built-in exceptions
ValueError, TypeError, KeyError, etc.
Create custom exceptions for domain-specific errors
class MyError(Exception): pass
Use bare 'raise' to re-raise current exception
Raise for exceptional cases, return for expected outcomes
What's Next?
You can now create and throw errors! But how do you find bugs in the first place? In the next lesson, we'll learn debugging basics – techniques to find and fix problems in your code.
