Skip to content
Theory · Exceptions · lesson 8 of 11

Custom exceptions

Picture a warehouse where every problem gets the same sign: "Error". The tea ran out—"Error". A typo in the delivery note—also "Error". The storekeeper sees the sign but does not know whether to run for a new batch or fix the papers.

The built-in ValueError is such a general sign: int("ten") raises it, and so do hundreds of other functions. If a warehouse raises a ValueError when an item runs out, callers cannot tell that case from a typo in a number. You need a sign of your own with exact wording.

A custom exception is a class that inherits from Exception. A descendant of a class is also called its subclass:

Python
class OutOfStockError(Exception):
    pass


def take(stock, name):
    if stock.get(name, 0) == 0:
        raise OutOfStockError(f"no {name} left")
    stock[name] -= 1


stock = {"tea": 0}
try:
    take(stock, "tea")
except OutOfStockError as e:
    print("error:", e)  # error: no tea left

Let's go step by step:

  1. The OutOfStockError class is declared as a descendant of Exception.
  2. The warehouse has no tea left, so stock.get(name, 0) gives 0.
  3. The take function raises OutOfStockError, and the line stock[name] -= 1 does not run.
  4. The except OutOfStockError branch catches the exception and prints error: no tea left.

A class with no body

The OutOfStockError class needs nothing but its parent, so its body is pass. It gets everything it needs from Exception:

  • a message in parentheses when it is created;
  • printing that message with print;
  • working with raise and try/except.

By convention, the name ends in Error: the name then shows that this is an exception.

The value of the class lies in the type itself: except OutOfStockError catches only this error and lets the others through. There is no need to compare the message text to learn the cause. This is the sign with exact wording.

Data in attributes

You can add data to an exception in attributes. A custom __init__ passes the message to the parent through super().__init__() and stores the rest in self:

Python
class OutOfStockError(Exception):
    def __init__(self, name):
        super().__init__(f"no {name} left")
        self.name = name


try:
    raise OutOfStockError("tea")
except OutOfStockError as e:
    print(e, "/", e.name)  # no tea left / tea

Let's see where each part comes from:

  • print(e) shows the message passed to super().__init__();
  • e.name is the attribute stored in __init__.

The calling code gets the item name ready to use and does not extract it from the text.

Catching by the parent

Custom exceptions can inherit from each other. Then an except with the parent class catches all its descendants, the way except Exception catches ValueError:

Python
class ShopError(Exception):
    pass


class OutOfStockError(ShopError):
    pass


try:
    raise OutOfStockError("no tea left")
except ShopError as e:
    print("shop error:", e)  # shop error: no tea left

The calling code chooses how precise to be:

  • except OutOfStockError for one case;
  • except ShopError for any warehouse error.

Here the sign picture ends: a real sign carries one wording, while an exception belongs to every class above it at once. The parent can also be another built-in class. An exception declared with class NegativeAmountError(ValueError): is also caught by an except ValueError branch written earlier.

Interviewers ask: "How do you create your own exception?" The answer:

  • declare a class that descends from Exception, not BaseException;
  • by convention, end its name in Error;
  • if needed, add an __init__ with attributes, and raise it with raise like a built-in one.

Common mistake

The class inherits from BaseException:

Python
class StopError(BaseException):  # ✗
    pass


try:
    raise StopError("stop")
except Exception:
    print("caught")
TEXT
StopError: stop

The except Exception branch catches only descendants of Exception, and StopError is not one of them. The program stops with a traceback, as it does from Ctrl+C. Only orders to stop, such as KeyboardInterrupt, inherit from BaseException directly, and a warehouse error is not an order to stop. Inherit your exceptions from Exception.

Key points

  • A custom exception is a class that descends from Exception; its body is often only pass, and its name ends in Error.
  • A class of your own lets you catch only this error without parsing the message text.
  • Data goes into attributes through a custom __init__, and the message goes to super().__init__().
  • An except with a parent class catches all descendants; custom exceptions inherit from Exception, not from BaseException.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way