До сих пор программа сообщала об ошибках встроенными ValueError и TypeError. Но в кассе ошибки бывают
свои: товара нет на складе, оплата не прошла, строка заказа записана неправильно. Код, который вызывает
кассу, хочет отличать их друг от друга и от настоящих ошибок программы. Для этого объявляют свои
исключения (exception).
Исключение — это класс
Каждое исключение — экземпляр класса. Почти все классы исключений — и все, с которыми вы встречались, включая
ValueError, — наследуют от Exception (выше него только BaseException для служебных вроде
KeyboardInterrupt — Ctrl+C). Своё исключение — подкласс Exception; часто ему не нужно ничего, кроме имени. По соглашению имя
заканчивается на Error:
class RegisterClosedError(Exception):
pass
def open_check(hour):
if hour < 8 or hour >= 22:
raise RegisterClosedError(f"касса закрыта в {hour}:00")
return "чек открыт"
print(open_check(9))
try:
open_check(23)
except RegisterClosedError as error:
print(type(error).__name__, "—", error)чек открыт RegisterClosedError — касса закрыта в 23:00
Иерархия исключений
Свои исключения удобно собрать в иерархию: общий базовый класс для всех ошибок кассы и подклассы для
конкретных случаев. Тогда except ShopError перехватит любую ошибку кассы, а except OutOfStockError —
только нехватку товара. Работает обычный isinstance: исключение подкласса — это и исключение базового класса.
class DeliveryError(Exception):
pass
class CourierLateError(DeliveryError):
pass
class AddressError(DeliveryError):
pass
for problem in [CourierLateError("курьер опаздывает"), AddressError("нет такого дома")]:
try:
raise problem
except DeliveryError as error: # перехватит оба подкласса
print(type(error).__name__, "→", error)
print(issubclass(AddressError, DeliveryError), issubclass(AddressError, Exception))CourierLateError → курьер опаздывает AddressError → нет такого дома True True
Объявите три класса исключений: ShopError — базовый для всех ошибок кассы, подкласс Exception;
OutOfStockError и PaymentError — подклассы ShopError.
pass; важны только базовые классы в скобках.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# объявите ShopError, OutOfStockError и PaymentErrorУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class ShopError(Exception):
pass
class OutOfStockError(ShopError):
pass
class PaymentError(ShopError):
passАтрибуты исключения
Исключение — объект, и у него могут быть атрибуты: какой товар, сколько просили, сколько есть. Код, который
его перехватил, достанет подробности, а не будет разбирать текст сообщения. Текст же по-прежнему передают
в __init__ базового класса — его покажет str(error):
class PaymentDeclinedError(Exception):
def __init__(self, amount, reason):
super().__init__(f"оплата {amount} ₽ отклонена: {reason}")
self.amount = amount
self.reason = reason
try:
raise PaymentDeclinedError(640, "нет связи с банком")
except PaymentDeclinedError as error:
print(error)
print(error.amount, error.reason)оплата 640 ₽ отклонена: нет связи с банком 640 нет связи с банком
Допишите OutOfStockError (по-прежнему подкласс ShopError): OutOfStockError("Сироп", 2, 1) хранит
название товара, сколько просили и сколько есть — в атрибутах name, requested и available, а
str(error) — "мало «Сироп»: нужно 2, есть 1".
__init__ записывает три атрибута и передаёт текст в super().__init__(…).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class OutOfStockError(ShopError):
def __init__(self, name, requested, available):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class OutOfStockError(ShopError):
def __init__(self, name, requested, available):
super().__init__(f"мало «{name}»: нужно {requested}, есть {available}")
self.name = name
self.requested = requested
self.available = availableНапишите функцию sell(stock, name, qty): stock — словарь «товар → количество». Если товара меньше qty
(или его нет вовсе), функция выбрасывает OutOfStockError с подробностями и склад не меняет; иначе —
уменьшает количество.
raise OutOfStockError(name, qty, available) до изменения склада.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
def sell(stock, name, qty):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
def sell(stock, name, qty):
available = stock.get(name, 0)
if available < qty:
raise OutOfStockError(name, qty, available)
stock[name] = available - qtyraise … from: цепочка исключений
Иногда своё исключение возникает из-за чужого: строку заказа "Латте x два" не удалось разобрать, потому что
int("два") выбросил ValueError. Перехватить ValueError и выбросить своё — правильно, но причину терять не
стоит. raise НовоеИсключение(…) from err создаёт цепочку исключений (exception chaining): причина
сохраняется в атрибуте __cause__, и трассировка покажет обе ошибки.
class PriceFormatError(Exception):
pass
def parse_price(text):
try:
return int(text.replace("₽", ""))
except ValueError as err:
raise PriceFormatError(f"цена не разобрана: {text}") from err
try:
parse_price("двести ₽")
except PriceFormatError as error:
print(error)
print("причина:", type(error.__cause__).__name__, "—", error.__cause__)цена не разобрана: двести ₽ причина: ValueError — invalid literal for int() with base 10: 'двести '
Объявите исключение OrderFormatError (подкласс ShopError) и функцию parse_qty(text): она возвращает
число из строки вида "x2" — после буквы x. Если число не разбирается, функция выбрасывает
OrderFormatError("неверное количество: x два") с исходным ValueError в качестве причины.
text[1:] и int внутри try.except ValueError as err: — своё исключение с from err.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class OrderFormatError(ShopError):
pass
def parse_qty(text):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class OrderFormatError(ShopError):
pass
def parse_qty(text):
try:
return int(text[1:])
except ValueError as err:
raise OrderFormatError(f"неверное количество: {text}") from errOutOfStockError — подкласс ShopError. Какой блок сработает?
try:
sell(stock, "Сироп", 10)
except ShopError:
print("ошибка кассы")
except OutOfStockError:
print("нет товара")except проверяются сверху вниз, и срабатывает первый подходящий. OutOfStockError — это и ShopError, поэтому сработал первый блок; второй тут не сработает никогда. Конкретные исключения ставят выше общих.