Skip to content
FollowtheDamnCode

Raising Exceptions

Signalling problems from your own code.

4 minute read

Big idea

Your own code can raise exceptions too: raise ValueError("..."). It's how a function says "I can't do this with what you gave me" — loudly, so the problem can't be ignored.

raise

def set_age(age):
    if age < 0:
        raise ValueError("age can't be negative")
    return age

print(set_age(30))    # 30
print(set_age(-5))    # stops the program

The first call prints 30. The second stops the program with ValueError: age can't be negative — nothing after it runs. Pick the built-in exception that fits best: ValueError for a bad value, TypeError for the wrong type.

Raising and catching together

The function raises; the caller decides what to do:

Predict: which lines print?
def withdraw(balance, amount):
    if amount > balance:
        raise ValueError("insufficient funds")
    return balance - amount

balance = 100
for amount in [30, 500, 50]:
    try:
        balance = withdraw(balance, amount)
        print("OK, balance", balance)
    except ValueError as e:
        print("Refused:", e)
What does it print? Show the answer
OK, balance 70
Refused: insufficient funds
OK, balance 20

Run and edit it in the app.

Why raise instead of printing an error or returning -1? Because a returned -1 can be silently used as if it were a real balance. An exception can't be ignored by accident — if nobody catches it, the program stops and says why.

Your own exception types

For errors specific to your program, make a new exception class by building on Exception (classes come in the OOP module — this is the whole recipe):

A custom exception
class OutOfStock(Exception):
    pass

def buy(stock, item):
    if stock.get(item, 0) == 0:
        raise OutOfStock(item)
    stock[item] -= 1

stock = {"tea": 1}
for item in ["tea", "tea"]:
    try:
        buy(stock, item)
        print("Bought", item)
    except OutOfStock as e:
        print("Sorry, out of", e)
output
Bought tea
Sorry, out of tea

Run and edit it in the app.

assert

assert condition, "message" raises an AssertionError if the condition is False. It's for checking your own assumptions while developing — not for validating user input.

assert len(scores) > 0, "scores should never be empty here"
Fix the bug: reject a zero divisor with a clear error
def safe_ratio(a, b):
    if b == 0:
        print("b must not be zero")
    return a / b

try:
    safe_ratio(1, 0)
except ValueError as e:
    print("Caught:", e)
output
Caught: b must not be zero

Run and edit it in the app.

Printing doesn't stop the function, so it carries on to a / 0 and raises a ZeroDivisionError the caller isn't expecting. Replace the print with raise ValueError("b must not be zero") — then the caller's except ValueError catches it and shows the message.

Why raise an exception rather than return an error value like -1?

Watch it

Think of it like

A cashier who spots a fake banknote doesn't quietly hand back the wrong change — they stop and call the manager. Raising is calling the manager; except is the manager deciding what happens next.

Key terms

  • raise — Signal an exception from your own code.
  • Custom exception — Your own exception class, built on Exception.
  • assert — Raises AssertionError if a condition you expected is False.

Check yourself

Which line raises an exception?

Which exception fits "the value is out of range"?

How do you make your own exception type?