Заказы приходят в кассу «Кофейной лавки» строками вида "Латте;220;2" — название, цена, количество. Строки
пишут люди, и в них бывают ошибки: отрицательная цена, буква вместо числа, пустой заказ. Касса не должна
падать из-за одной плохой строки — она должна провести хорошие заказы и объяснить, что не так с остальными.
В проекте собран весь модуль: dataclass, __post_init__, frozen=True, field(default_factory=list) и свои
исключения.
Шаг 1. Ошибки заказа
Объявите ShopError — базовое исключение кассы (подкласс Exception) — и два его подкласса:
InvalidLineError (строка заказа неверна) и EmptyOrderError (в заказе нет ни одной строки).
pass; важны базовые классы в скобках.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# объявите ShopError, InvalidLineError и EmptyOrderErrorУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class ShopError(Exception):
pass
class InvalidLineError(ShopError):
pass
class EmptyOrderError(ShopError):
passШаг 2. Строка заказа
Объявите неизменяемый dataclass OrderLine с полями product (строка), price (целое) и qty (целое, по
умолчанию 1) и свойством cost — цена на количество. Если цена или количество не больше нуля, создание
выбрасывает InvalidLineError с текстом "неверная строка: Чай, цена -120, количество 1".
@dataclass(frozen=True); проверка — в __post_init__.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
from dataclasses import dataclass, field
class OrderLine:
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class OrderLine:
product: str
price: int
qty: int = 1
def __post_init__(self):
if self.price <= 0 or self.qty <= 0:
raise InvalidLineError(f"неверная строка: {self.product}, цена {self.price}, количество {self.qty}")
@property
def cost(self):
return self.price * self.qtyШаг 3. Заказ
Объявите dataclass Order с полями number (целое) и lines — список строк заказа, у каждого заказа свой,
по умолчанию пустой. Метод add(line) добавляет строку, свойство total — сумма cost всех строк. Метод
close() возвращает total, а если строк нет — выбрасывает EmptyOrderError("заказ №18 пуст") (с номером
этого заказа).
lines: list = field(default_factory=list).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Order:
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
@dataclass
class Order:
number: int
lines: list = field(default_factory=list)
def add(self, line):
self.lines.append(line)
@property
def total(self):
return sum(line.cost for line in self.lines)
def close(self):
if not self.lines:
raise EmptyOrderError(f"заказ №{self.number} пуст")
return self.totalШаг 4. Разбор строки
Напишите функцию parse_line(text): из "Латте;220;2" она делает OrderLine("Латте", 220, 2). Если частей не
три или цена и количество не разбираются в целые числа — InvalidLineError("не разобрать строку: <текст>"),
а при ошибке int — с исходным ValueError в качестве причины (raise … from). Ошибки самой OrderLine
(отрицательная цена) пусть уходят дальше как есть.
parts = text.split(";"); если len(parts) != 3 — сразу raise.int(...) — внутри try, OrderLine(...) — уже после него, чтобы её InvalidLineError не превратилась в «не разобрать строку».Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
def parse_line(text):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
def parse_line(text):
parts = text.split(";")
if len(parts) != 3:
raise InvalidLineError(f"не разобрать строку: {text}")
name, price, qty = parts
try:
price, qty = int(price), int(qty)
except ValueError as err:
raise InvalidLineError(f"не разобрать строку: {text}") from err
return OrderLine(name, price, qty)Шаг 5. Касса за день
В словаре day — номера заказов и их строки. Для каждого заказа по порядку: создайте Order, разберите и
добавьте строки, закройте заказ. Сохраните в results по строке на заказ: "№17: 580 ₽" для закрытого и
"№18: ошибка — заказ №18 пуст" для заказа, на котором выброшено исключение кассы (текст — str исключения).
Перехватывайте только ShopError — другие ошибки пусть видны.
try: первая же ошибка прекращает работу с ним.except ShopError as error: и f"№{number}: ошибка — {error}".Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
day = {
17: ["Латте;220;2", "Круассан;140;1"],
18: [],
19: ["Раф;260;1", "Чай;-120;1"],
20: ["Какао;190;x2"],
21: ["Эспрессо;150;3"],
}
results = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
day = {
17: ["Латте;220;2", "Круассан;140;1"],
18: [],
19: ["Раф;260;1", "Чай;-120;1"],
20: ["Какао;190;x2"],
21: ["Эспрессо;150;3"],
}
results = []
for number, texts in day.items():
try:
order = Order(number)
for text in texts:
order.add(parse_line(text))
results.append(f"№{number}: {order.close()} ₽")
except ShopError as error:
results.append(f"№{number}: ошибка — {error}")Итог
print("Касса за день")
for line in results:
print(" ", line)
closed = [line for line in results if "ошибка" not in line]
print(f"проведено заказов: {len(closed)} из {len(results)}")Касса за день №17: 580 ₽ №18: ошибка — заказ №18 пуст №19: ошибка — неверная строка: Чай, цена -120, количество 1 №20: ошибка — не разобрать строку: Какао;190;x2 №21: 450 ₽ проведено заказов: 2 из 5
Из пяти заказов прошли два: №17 на 580 ₽ и №21 на 450 ₽. Остальные касса не провела, но объяснила почему:
пустой заказ, отрицательная цена чая, буква в количестве какао. Ни одна ошибка не уронила кассу, а ошибки
в самом коде — опечатки, неверные вызовы — except ShopError не спрятал бы.
Иерархия исключений сделала отчёт простым: касса знает об одном классе ShopError и не перечисляет все
виды ошибок. Новая проверка — скажем, «товара нет в меню» — это ещё один подкласс, и цикл дня не меняется.
Остался итоговый проект: собрать всё, что было в курсе, в одну кассу.