Перейти к содержимому
Теория · Исключения · урок 8 из 11

Свои исключения

Представьте склад, где на любую неполадку вешают одну и ту же табличку «Ошибка». Кончился чай — «Ошибка». Опечатка в накладной — тоже «Ошибка». Кладовщик видит табличку, но не знает, бежать ли за новой партией или исправлять бумаги.

Встроенный ValueError — такая общая табличка: его возбуждают и int("ten"), и сотни других функций. Если склад возбуждает ValueError, когда товар кончился, вызывающий код не отличит эту ситуацию от опечатки в числе. Нужна своя табличка с точной надписью.

Своё исключение — это класс, унаследованный от Exception. Потомка класса называют также его подклассом:

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

Давайте разберём по шагам:

  1. Класс OutOfStockError объявлен как потомок Exception.
  2. На складе чая не осталось, поэтому stock.get(name, 0) даёт 0.
  3. Функция take возбуждает OutOfStockError, и строка stock[name] -= 1 не выполняется.
  4. Ветка except OutOfStockError ловит исключение и печатает error: no tea left.

Класс без тела

Классу OutOfStockError не нужно ничего, кроме родителя, поэтому в его теле стоит pass. От Exception ему достаётся всё нужное:

  • сообщение в скобках при создании;
  • вывод этого сообщения через print;
  • работа с raise и try/except.

Имя по соглашению заканчивается на Error: так из названия видно, что это исключение.

Ценность класса — в самом типе: except OutOfStockError ловит только эту ошибку и пропускает остальные. Сравнивать текст сообщения, чтобы узнать причину, не нужно. Это и есть табличка с точной надписью.

Данные в атрибутах

Исключению можно добавить данные в атрибутах. Свой __init__ передаёт сообщение родителю через super().__init__() и сохраняет остальное в 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

Разберём, что откуда берётся:

  • print(e) показывает сообщение, переданное в super().__init__();
  • e.name — атрибут, сохранённый в __init__.

Вызывающий код получает название товара готовым и не выделяет его из текста.

Ловля по родителю

Свои исключения можно наследовать друг от друга. Тогда except с родительским классом ловит всех его потомков, как except Exception ловит 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

Вызывающий код сам выбирает точность:

  • except OutOfStockError — для одного случая;
  • except ShopError — для любой ошибки склада.

Здесь аналогия с табличками заканчивается: на настоящей табличке одна надпись, а исключение принадлежит сразу всем классам выше себя. Родителем может быть и другой встроенный класс. Исключение, объявленное строкой class NegativeAmountError(ValueError):, поймает и уже написанная ветка except ValueError.

На собеседовании спрашивают: «Как создать своё исключение?» Ответ:

  • объявите класс-потомок Exception, а не BaseException;
  • по соглашению закончите имя на Error;
  • при необходимости добавьте __init__ с атрибутами и возбуждайте его через raise, как встроенное.

Частая ошибка

Класс унаследовали от BaseException:

Python
class StopError(BaseException):  # ✗
    pass


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

Ветка except Exception ловит только потомков Exception, а StopError к ним не относится. Программа останавливается с трассировкой, как от Ctrl+C. Прямо от BaseException наследуют только команды остановки вроде KeyboardInterrupt, а ошибка склада — не команда остановки. Наследуйте свои исключения от Exception.

Главное

  • Своё исключение — класс-потомок Exception; часто его тело — один pass, а имя заканчивается на Error.
  • Свой класс позволяет ловить только эту ошибку, не разбирая текст сообщения.
  • Данные кладут в атрибуты через свой __init__, а сообщение передают в super().__init__().
  • except с родительским классом ловит всех потомков; свои исключения наследуют от Exception, а не от BaseException.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом