Raising Exceptions
You can throw exceptions yourself with raise — the right way to enforce rules and reject invalid data in your functions.
"raise is a full stop. It says: something is so wrong here that I refuse to continue. The caller must deal with this."
— ShurAIThe raise Statement
So far, exceptions happened automatically when something went wrong. With raise, you decide when to throw an error. This lets you enforce rules inside your functions:
raise ValueError("Something went wrong")
# ValueError: Something went wrong
This is the pattern for every raise: the exception type, then a message in parentheses that explains what went wrong.
Validating Function Input
The most common use of raise is rejecting bad input before doing any real work:
def set_age(age):
"""Set a person's age. Must be 0-150."""
if not isinstance(age, int):
raise TypeError(f"Age must be an integer, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age must be between 0 and 150, got {age}")
print(f"Age set to {age}")
set_age(25) # Age set to 25
set_age(-1) # ValueError: Age must be between 0 and 150, got -1
set_age("old") # TypeError: Age must be an integer, got str
✗ Bad data keeps moving through your program
✗ Bug surfaces somewhere far from the cause
✓ Precise message about what went wrong
✓ Caller must acknowledge and handle it
Catching What You Raise
Functions that raise are meant to be called inside try blocks:
def make_username(name):
"""Create a username. Name must be 3-20 characters."""
if len(name) < 3:
raise ValueError("Name too short. Minimum 3 characters.")
if len(name) > 20:
raise ValueError("Name too long. Maximum 20 characters.")
return name.lower().replace(" ", "_")
for name in ["Riya", "AI", "Superlongusernamethatfails"]:
try:
user = make_username(name)
print(f"Created: {user}")
except ValueError as e:
print(f"Invalid name '{name}': {e}")
Created: riya
Invalid name 'AI': Name too short. Minimum 3 characters.
Invalid name 'Superlongusernamethatfails': Name too long. Maximum 20 characters.
Re-raising an Exception
Sometimes you want to log an error but still let it propagate up. Use bare raise inside an except block:
def load_config(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
print(f"[LOG] Config file missing: {path}")
raise # re-raise the same exception to the caller
Real Example — Bank Account Deposit
def deposit(balance, amount):
"""Add funds to account. Amount must be positive."""
if not isinstance(amount, (int, float)):
raise TypeError("Amount must be a number")
if amount <= 0:
raise ValueError(f"Deposit amount must be positive, got {amount}")
return balance + amount
balance = 1000
for amount in [500, -100, "free money", 250]:
try:
balance = deposit(balance, amount)
print(f"Deposited {amount}. Balance: {balance}")
except (ValueError, TypeError) as e:
print(f"Deposit failed: {e}")
Deposited 500. Balance: 1500
Deposit failed: Deposit amount must be positive, got -100
Deposit failed: Amount must be a number
Deposited 250. Balance: 1750
"Raise early. The earlier you catch a bad value, the easier the bug is to fix. An error at the function boundary is ten times better than a mysterious crash five steps later."
— ShurAI🧠 Quiz — Q1
What does raise ValueError("bad input") do?
🧠 Quiz — Q2
Why is raising an exception often better than returning None or -1 on error?
🧠 Quiz — Q3
What does bare raise (with nothing after it) do inside an except block?
🧠 Quiz — Q4
Which exception type should you raise when a function receives the right type but an invalid value (e.g. age = -5)?