Свои исключения
Представьте склад, где на любую неполадку вешают одну и ту же табличку «Ошибка». Кончился чай — «Ошибка». Опечатка в накладной — тоже «Ошибка». Кладовщик видит табличку, но не знает, бежать ли за новой партией или исправлять бумаги.
Встроенный ValueError — такая общая табличка: его возбуждают и int("ten"), и сотни других функций. Если склад возбуждает ValueError, когда товар кончился, вызывающий код не отличит эту ситуацию от опечатки в числе. Нужна своя табличка с точной надписью.
Своё исключение — это класс, унаследованный от Exception. Потомка класса называют также его подклассом:
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Давайте разберём по шагам:
- Класс
OutOfStockErrorобъявлен как потомокException. - На складе чая не осталось, поэтому
stock.get(name, 0)даёт0. - Функция
takeвозбуждаетOutOfStockError, и строкаstock[name] -= 1не выполняется. - Ветка
except OutOfStockErrorловит исключение и печатаетerror: no tea left.
Класс без тела
Классу OutOfStockError не нужно ничего, кроме родителя, поэтому в его теле стоит pass. От Exception ему достаётся всё нужное:
- сообщение в скобках при создании;
- вывод этого сообщения через
print; - работа с
raiseиtry/except.
Имя по соглашению заканчивается на Error: так из названия видно, что это исключение.
Ценность класса — в самом типе: except OutOfStockError ловит только эту ошибку и пропускает остальные. Сравнивать текст сообщения, чтобы узнать причину, не нужно. Это и есть табличка с точной надписью.
Данные в атрибутах
Исключению можно добавить данные в атрибутах. Свой __init__ передаёт сообщение родителю через super().__init__() и сохраняет остальное в self:
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:
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:
class StopError(BaseException): # ✗
pass
try:
raise StopError("stop")
except Exception:
print("caught")StopError: stopВетка except Exception ловит только потомков Exception, а StopError к ним не относится. Программа останавливается с трассировкой, как от Ctrl+C. Прямо от BaseException наследуют только команды остановки вроде KeyboardInterrupt, а ошибка склада — не команда остановки. Наследуйте свои исключения от Exception.
Главное
- Своё исключение — класс-потомок
Exception; часто его тело — одинpass, а имя заканчивается наError. - Свой класс позволяет ловить только эту ошибку, не разбирая текст сообщения.
- Данные кладут в атрибуты через свой
__init__, а сообщение передают вsuper().__init__(). exceptс родительским классом ловит всех потомков; свои исключения наследуют отException, а не отBaseException.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.