Legends ofPythos
Claim your name
Writing Real Programs

Raising your own exceptions

Lesson 9 of 16

Watch the lesson1:09 · with Sigrid
So far, you have learned to catch errors with try and except. But sometimes the error is not a surprise. It is something you know about in advance: an argument that cannot be right.

Instead of silently returning a bad value or crashing later, Python lets you stop execution immediately by raising your own exception. This tells every caller, "the input was invalid; I am giving up." The message you attach to the exception becomes part of the traceback.

raise with a clear message

def divide(a, b):
    if b == 0:
        raise ValueError("b must not be zero")
    return a / b

# Calling divide(10, 0) raises ValueError with the message above.
print(divide(10, 2))   # works fine -> 5.0
A function that refuses to do impossible work
raise takes an exception object. The most common built-in types are ValueError, TypeError, and KeyError. Pick the one whose name best describes what went wrong, then pass a short human-readable string. That string is what shows up in tracebacks, so write it like you would explain to another developer.

Validating arguments

def set_temperature(celsius):
    if not isinstance(celsius, (int, float)):
        raise TypeError("celsius must be a number")
    if celsius < -273.15 or celsius > 10_000:
        raise ValueError("temperature out of plausible range")
    return f"{celsius} C"

print(set_temperature(20))          # 20 C
# set_temperature('hot')            -> TypeError with a clear message
Two checks, two different exception types

Letting it travel up

def inner(x):
    if x < 0:
        raise ValueError("inner: negative not allowed")
    return x * 2

def outer(x):
    # No try/except here. The exception keeps climbing.
    result = inner(x) + 1
    return result

# If you call outer(-5), the traceback will show BOTH frames:
#   File ..., in outer: result = inner(x) + 1
#   File ..., in inner: raise ValueError(...)
Exceptions propagate through every function frame until someone catches them
You do not have to catch an exception at the level where it is raised. It bubbles up through inner, then outer, and so on, all the way to a top-level try/except or into your test runner's output. This keeps low-level functions simple: they validate their own inputs and let higher layers decide how (or whether) to recover.

Your turn

0 of 2 solved

Exercise 1

+35 XP
Write set_age(age) that returns 'OK' for a valid age. Raise TypeError if age is not a number, and ValueError if it is below 0 or above 150.
def set_age(age):
    pass

Run your code to check it against the tests.

Exercise 2

+35 XP
Write two functions that work together:
  1. parse_score(raw) — takes a value and returns it as an int if possible. If raw is not convertible to int (e.g. 'abc', None, [1]), raise ValueError with the exact message 'bad score input'.
  2. submit_scores(scores) — takes a list of raw values, parses each one via parse_score, and returns the parsed ints as a new list in order. If ANY element is invalid, let that ValueError propagate up to the caller (do NOT catch it inside submit_scores).
Both functions must be defined at module level.
def parse_score(raw):
    pass


def submit_scores(scores):
    pass

Run your code to check it against the tests.