raise
The raise statement in Python explicitly triggers an exception. It’s how you signal that something has gone wrong in your code—whether that’s invalid input, a violated constraint, or an unexpected state. Rather than silently continuing with bad data, raise stops execution and propagates an error up the call stack.
Syntax
raise # Re-raise the current exception (must be in an except block)
raise ValueError("error message") # Raise a built-in exception
raise CustomError("custom message") # Raise a custom exception class
Basic Usage
Raise a built-in exception with a message:
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
divide(10, 0)
# Traceback (most recent call last):
# File "<stdin>", line 3, in divide
# ZeroDivisionError: Cannot divide by zero
Raise different exception types for different errors:
def process_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age seems unrealistic")
return age
process_age("twenty")
# TypeError: Age must be an integer
process_age(-5)
# ValueError: Age cannot be negative
Raising Custom Exceptions
Define your own exception classes for domain-specific errors:
class ValidationError(Exception):
"""Raised when input fails validation."""
pass
class AuthenticationError(Exception):
"""Raised when authentication fails."""
pass
def login(username, password):
if not verify_credentials(username, password):
raise AuthenticationError("Invalid username or password")
return True
Add custom behavior to exception classes:
class APIError(Exception):
def __init__(self, message, status_code=None):
super().__init__(message)
self.status_code = status_code
def fetch_data(url):
if not url.startswith("https://"):
raise APIError("URL must use HTTPS", status_code=400)
# ... fetch logic
try:
fetch_data("http://example.com")
except APIError as e:
print(f"Error {e.status_code}: {e}")
# Error 400: URL must use HTTPS
Re-Raising Exceptions
Use raise alone in an except block to re-raise the current exception:
def outer_wrapper():
try:
risky_operation()
except ValueError as e:
print(f"Caught error: {e}")
raise # Re-raise the same exception
def handle_with_context():
try:
outer_wrapper()
except ValueError:
print("Error propagated to top level")
raise # Continues propagating
This pattern lets you log errors while still propagating them:
import logging
def read_config(path):
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError as e:
logging.warning(f"Config file not found: {path}")
raise # Caller needs to know the file is missing
except json.JSONDecodeError as e:
logging.error(f"Invalid JSON in {path}: {e}")
raise # Config is invalid—caller must handle
Exception Chaining
Python 3 supports exception chaining with from:
try:
int("not a number")
except ValueError as e:
raise RuntimeError("Conversion failed") from e
Output:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: invalid literal for int()...
The above exception was the direct cause of the following:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Conversion failed
Use from None to suppress the original exception context:
try:
sensitive_operation()
except PermissionError:
raise AppError("Operation failed") from None
Raising in Different Contexts
In functions
def validate_email(email):
if "@" not in email:
raise ValueError(f"Invalid email: {email}")
return True
def register_user(email, name):
validate_email(email) # May raise ValueError
# ... continue registration
In classes
class BankAccount:
def __init__(self, balance):
if balance < 0:
raise ValueError("Balance cannot be negative")
self._balance = balance
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
return amount
In init and special methods
class PositiveInteger:
def __init__(self, value):
if not isinstance(value, int) or value <= 0:
raise TypeError("Must be a positive integer")
self.value = value
class OrderedPair:
def __setattr__(self, name, value):
if not hasattr(self, "_values_set") and len(self.__dict__) >= 2:
raise AttributeError("Cannot set more than 2 values")
super().__setattr__(name, value)
In generators
def number_stream(n):
for i in range(n):
if i == 13:
raise ValueError("Unlucky number")
yield i
Practical Patterns
Validation decorator
def validate_positive(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if result < 0:
raise ValueError(f"{func.__name__} returned negative value")
return result
return wrapper
@validate_positive
def calculate_diff(a, b):
return a - b
Assert vs raise
Use assert for debugging and internal invariants, raise for runtime errors:
def divide(a, b):
# Assert for programmer errors during development
assert b != 0, "Developer error: divide by zero"
# Raise for runtime errors from bad input
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
Sentinel values vs raise
Instead of returning special values, raise exceptions:
# Bad - magic values
def find_user(users, name):
for user in users:
if user["name"] == name:
return user
return None # Magic value
# Better - raise exception
def find_user(users, name):
for user in users:
if user["name"] == name:
return user
raise KeyError(f"User not found: {name}")
See Also
- try / except / finally — catching exceptions
- class keyword — defining custom exceptions
- def keyword — defining functions that may raise
- for keyword — loop control in Python