Raising exceptions with raise
Overview
The `raise` statement forces an exception to occur immediately. You use it to signal that an error condition has happened, passing the problem up to whoever called your function. def set_age(age): if age < 0: raise ValueError("Age cannot be negative") You instantiate an exception to halt execution. Inside an `except` block, calling a bare `raise` with no arguments re-raises the current exception with its original traceback preserved. try: set_age(-5) except ValueError: print("Logging error...") raise Unlike `raise err`, which can reset the traceback, a bare `raise` lets you log an error but still pass it up the chain untouched. def parse_payload(payload): try: user_id = payload["user_id"] except KeyError as e: raise ValueError("Invalid payload") # Trap: The original KeyError traceback is lost! When translating one exception into another, a simple `raise` obscures the root cause. You must use `raise NewError() from e` to link them, covered fully in exception-chaining.
Functions should rarely fail silently. If a function is asked to do something impossible, it should scream loudly by raising an exception. This forces the calling code to deal with the problem rather than continuing with a corrupted state or returning confusing `None` values. def withdraw(balance, amount): if amount > balance: raise ValueError("Insufficient funds") return balance - amount Choosing the right built-in type helps callers know what went wrong. For example, use `ValueError` for bad data, or `TypeError` for the wrong data type.
Where used: Validating function inputs and enforcing rules, Re-raising errors after logging them, Signaling business logic failures (e.g., `InsufficientFunds`)
Why learn this
- Knowing how to actively protect your functions from bad input by raising `ValueError`
- Understanding the difference between `return None` (silent failure) and `raise` (loud failure)
- Learning how to intercept an error to log it, and then use a bare `raise` to pass it up
Code walkthrough
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
print("Processing...")
try:
process_payment(-50)
except ValueError as e:
print(f"Caught: {e}")
Focus: The `raise ValueError(...)` immediately stops execution of `process_payment`, skips the `'Processing...'` print, and jumps straight into the caller's `except` block.
Common mistakes
- Using exceptions for normal control flow: Exceptions are relatively slow. You shouldn't use `raise` to signal a normal, expected outcome (like hitting the end of a `for` loop). Raise them for exceptional, error conditions only.
- Raising the class instead of an instance: While `raise ValueError` works, it provides no context. You should always instantiate it with a helpful message: `raise ValueError('Invalid user ID')`.
Glossary
- instantiate
- The process of creating a specific object from a class blueprint. Example: `user = User()`.
- corrupted state
- When a program's internal data becomes invalid, inconsistent, or broken, often leading to unpredictable behavior. Example: `balance < 0`.
Recall questions
- What happens if you use the `raise` keyword without any arguments inside an `except` block?
- If a function receives a `str` when it explicitly requires an `int`, which built-in exception is the most appropriate to raise?
- If a function receives an `int`, but the integer is negative and the function requires a positive age, which built-in exception is the most appropriate to raise?
- Why is raising a new exception without `from e` inside an `except` block considered dangerous?
Understanding checks
What is wrong with this exception raising pattern?
The exception is raised as a class without an error message.
While technically valid Python, it's bad practice because `raise ValueError` provides no context. It should be instantiated with a helpful message: `raise ValueError('Cannot divide by zero')`.
What does a bare `raise` statement (with no arguments) do when used inside an `except` block?
It re-raises the exact exception that was just caught.
A bare `raise` maintains the original exception and its traceback, allowing you to log or inspect the error locally before letting it propagate up the call stack.
Practice tasks
Input validation
Modify the `set_age` function so that if `age` is less than `0`, it raises a `ValueError` with the message `'Age cannot be negative'`. Otherwise, it should return the `age`.
Challenge
Validate configuration
Write `check_config(cfg)` that takes a `dict`. If `'host'` is missing, raise a `KeyError`. If `'port'` is not an `int`, raise a `TypeError`. If `'port'` < `1024`, raise a `ValueError`.
Continue learning
Previous: try/except with specific exception types
Previous: Defining functions and return values
Next: Custom exception classes