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.
| Exception | Raise it when |
|---|---|
ValueError | the type is right but the value is not, such as a negative age |
TypeError | the value is the wrong type, such as text where a number belongs |
KeyError | a dictionary key is missing |
IndexError | a position is past the end of a list |
NotImplementedError | a subclass must override this method |
RuntimeError | something 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