← Python lessons

How to Raise Exceptions in Python (and Pick the Right One)

The raise statement stops your code with an exception on purpose. Pick the built-in exception that names the problem, such as ValueError or TypeError.

Read Try and except first.

Raise an exception

Write raise, the exception class and a message in brackets. The function stops at that line.

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

print(set_age(12))
print(set_age(-3))  # this line raises ValueError

set_age(12) returns normally. set_age(-3) raises, so nothing after it runs. The message says exactly what was wrong.

Pick the right exception

Callers catch exceptions by class, so choose the one that fits.

ExceptionRaise it when
ValueErrorthe type is right but the value is not, such as a negative age
TypeErrorthe value is the wrong type, such as text where a number belongs
KeyErrora dictionary key is missing
IndexErrora position is past the end of a list
NotImplementedErrora subclass must override this method
RuntimeErrorsomething went wrong that fits none of the above
def average(scores):
    if not isinstance(scores, list):
        raise TypeError("scores must be a list")
    if len(scores) == 0:
        raise ValueError("scores is empty")
    return sum(scores) / len(scores)

for data in [[4, 6], [], "46"]:
    try:
        print(average(data))
    except (TypeError, ValueError) as error:
        print(type(error).__name__, "-", error)

A string is the wrong type, so it gets TypeError. An empty list is the right type with a bad value, so it gets ValueError.

Re-raise an error after handling it

A bare raise inside an except block sends the same error on to the caller. Use it to log a problem without hiding it.

def load(text):
    try:
        return int(text)
    except ValueError:
        print("log: bad value", repr(text))
        raise

try:
    load("x")
except ValueError as error:
    print("caller got:", error)

load prints its log line, then the caller still receives the original ValueError.

Swap the error with raise from

raise NewError(...) from error replaces a low-level error with one that makes sense to the caller. The original is kept as its cause.

settings = {"volume": "11"}

def get_setting(name):
    try:
        return settings[name]
    except KeyError as error:
        raise LookupError(f"no setting called {name!r}") from error

print(get_setting("volume"))
try:
    get_setting("speed")
except LookupError as error:
    print(error)
    print("cause:", repr(error.__cause__))

The caller sees a clear LookupError. The KeyError is still there in error.__cause__ for debugging.

Note: Avoid raise Exception("..."). A caller can't catch it without also catching every other error. Raise a specific class, or make your own with Custom exceptions.

See also: Data validation, Custom exceptions.

Your turn

Make withdraw raise ValueError("not enough money") when amount is more than balance.

def withdraw(balance, amount):
    # add two lines: if amount is more than balance, raise the ValueError
    return balance - amount

print(withdraw(50, 20))
try:
    withdraw(50, 80)
except ValueError as error:
    print("Error:", error)

Expected output

30
Error: not enough money

Practise this in the free Python course, where every exercise checks your code instantly.