Python Custom Exceptions: Subclassing Exception
A custom exception is your own error class, made by subclassing Exception. It names a problem in your program, so callers can catch exactly that one.
Read Raising exceptions and Inheritance first.
Define a custom exception
A class that inherits from Exception is enough. It needs no body, so write
pass.
class OutOfStockError(Exception):
pass
def buy(stock, item):
if stock.get(item, 0) == 0:
raise OutOfStockError(f"{item} is sold out")
stock[item] -= 1
stock = {"apple": 1}
buy(stock, "apple")
try:
buy(stock, "apple")
except OutOfStockError as error:
print("Sorry:", error)
The first purchase works. The second raises OutOfStockError, and the
except catches only that class. End the name in Error, like the built-in
exceptions.
An uncaught custom exception
An exception nobody catches still stops the program. The error line shows your class name and message.
class ConfigError(Exception):
pass
raise ConfigError("missing 'level' setting") # this line raises ConfigError
The name tells the reader what failed before they read the message.
Store extra details on the exception
Give the class an __init__ to keep useful values as attributes. Pass the
message on to Exception with super().__init__().
class NotEnoughMoneyError(Exception):
def __init__(self, balance, price):
super().__init__(f"need {price}, have {balance}")
self.balance = balance
self.price = price
try:
raise NotEnoughMoneyError(20, 50)
except NotEnoughMoneyError as error:
print(error)
print("short by", error.price - error.balance)
print(error) shows the message. The caller can also read error.price and
error.balance to work out what to do next.
Group related errors under a base class
Make one base exception for your program, and subclass it for each problem. Catching the base class catches every one of them.
class GameError(Exception):
pass
class InvalidMoveError(GameError):
pass
class NotYourTurnError(GameError):
pass
for problem in [InvalidMoveError("square taken"), NotYourTurnError("wait for O")]:
try:
raise problem
except GameError as error:
print(type(error).__name__, "-", error)
One except GameError handles both. A caller that cares can still catch
InvalidMoveError on its own.
Note: Subclass Exception, not BaseException. BaseException is for
signals such as Ctrl+C, and except Exception does not catch it.
See also: Try and except, Method overriding and super().
Your turn
Define PasswordTooShortError as a subclass of Exception, so the checks
below work.
# add two lines: a PasswordTooShortError class that subclasses Exception
def check_password(password):
if len(password) < 8:
raise PasswordTooShortError(f"{len(password)} characters, need 8")
return "ok"
for password in ["hunter2", "correct-horse"]:
try:
print(check_password(password))
except PasswordTooShortError as error:
print("Too short:", error)
Expected output
Too short: 7 characters, need 8
ok