← Python lessons

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.

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

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