В итоговом проекте вы соберёте кассу «Кофейной лавки» целиком — из классов, которые писали по отдельности весь курс. Проект в двух уроках. В этом — модель: товары, каталог и корзина. В следующем — правила: скидки, оплата, чек и отчёт за день.
Почти всё здесь вы уже делали; новое — в том, чтобы решить, какой инструмент взять для каждой части:
| Часть кассы | Что это | Инструмент |
|---|---|---|
| ошибки | своя иерархия | подклассы Exception |
| товар | неизменяемые данные | @dataclass(frozen=True) |
| каталог | контейнер товаров | __len__, __contains__, __getitem__, __iter__ |
| корзина | содержит каталог и строки | композиция, свойство, магические методы |
Шаг 1. Ошибки кассы
Объявите базовое исключение кассы ShopError (подкласс Exception) и его подкласс UnknownProductError —
«товара нет в каталоге».
pass.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# объявите ShopError и UnknownProductErrorУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class ShopError(Exception):
pass
class UnknownProductError(ShopError):
passШаг 2. Товар
Объявите неизменяемый dataclass Product с полями name (строка), price (целое) и category (строка, например
"кофе"). Цена не больше нуля — ValueError при создании. str(product) — "Латте — 220 ₽".
@dataclass(frozen=True), проверка — в __post_init__, текст — в __str__.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
from dataclasses import dataclass, field
class Product:
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Product:
name: str
price: int
category: str
def __post_init__(self):
if self.price <= 0:
raise ValueError(f"цена должна быть больше нуля: {self.name}")
def __str__(self):
return f"{self.name} — {self.price} ₽"Шаг 3. Каталог
Объявите класс Catalog: Catalog([товар, товар, …]) хранит товары во внутреннем словаре «название → товар».
len(catalog)— число товаров;"Латте" in catalog— есть ли товар с таким названием;catalog["Латте"]— товар; если его нет —UnknownProductError("нет в каталоге: Латте");for product in catalog— товары по алфавиту названий.
__init__: self._products = {p.name: p for p in products}.__getitem__ сначала проверьте name not in self._products и выбросите своё исключение — иначе словарь выбросит KeyError.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Catalog:
def __init__(self, products):
...
def __len__(self):
...
def __contains__(self, name):
...
def __getitem__(self, name):
...
def __iter__(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Catalog:
def __init__(self, products):
self._products = {p.name: p for p in products}
def __len__(self):
return len(self._products)
def __contains__(self, name):
return name in self._products
def __getitem__(self, name):
if name not in self._products:
raise UnknownProductError(f"нет в каталоге: {name}")
return self._products[name]
def __iter__(self):
for name in sorted(self._products):
yield self._products[name]Шаг 4. Каталог из строк
Напишите функцию load_catalog(lines): из строк вида "Латте;220;кофе" она собирает Catalog. Затем загрузите
каталог кофейни из menu_lines в переменную catalog.
name, price, category = line.split(";") и Product(name, int(price), category).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
def load_catalog(lines):
...
menu_lines = [
"Эспрессо;150;кофе", "Капучино;220;кофе", "Латте;240;кофе", "Раф;260;кофе",
"Чай;120;чай", "Какао;190;другое", "Круассан;140;выпечка", "Маффин;130;выпечка",
]
catalog = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
def load_catalog(lines):
products = []
for line in lines:
name, price, category = line.split(";")
products.append(Product(name, int(price), category))
return Catalog(products)
menu_lines = [
"Эспрессо;150;кофе", "Капучино;220;кофе", "Латте;240;кофе", "Раф;260;кофе",
"Чай;120;чай", "Какао;190;другое", "Круассан;140;выпечка", "Маффин;130;выпечка",
]
catalog = load_catalog(menu_lines)for product in catalog:
print(f"{product} ({product.category})")Какао — 190 ₽ (другое) Капучино — 220 ₽ (кофе) Круассан — 140 ₽ (выпечка) Латте — 240 ₽ (кофе) Маффин — 130 ₽ (выпечка) Раф — 260 ₽ (кофе) Чай — 120 ₽ (чай) Эспрессо — 150 ₽ (кофе)
Шаг 5. Корзина
Строка корзины — товар и количество. Это маленький dataclass, он уже готов:
from dataclasses import dataclass
@dataclass
class Line:
product: Product
qty: int
@property
def cost(self):
return self.product.price * self.qty
print(Line(catalog["Латте"], 2), Line(catalog["Латте"], 2).cost)Line(product=Product(name='Латте', price=240, category='кофе'), qty=2) 480
Объявите класс Cart: Cart(catalog) — пустая корзина, которая содержит каталог и берёт товары из него.
add(name, qty=1)— найти товар в каталоге (неизвестный —UnknownProductErrorуходит дальше) и добавить строку; если строка с этим товаром уже есть — увеличить её количество, а не добавлять вторую;len(cart)— число строк,for line in cart— строки по порядку добавления;- свойство
subtotal— суммаcostвсех строк.
self._lines; для существующего товара найдите его строку циклом и прибавьте qty.Line изменяемый — line.qty += qty работает.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Cart:
def __init__(self, catalog):
...
def add(self, name, qty=1):
...
def __len__(self):
...
def __iter__(self):
...
@property
def subtotal(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Cart:
def __init__(self, catalog):
self.catalog = catalog
self._lines = []
def add(self, name, qty=1):
product = self.catalog[name]
for line in self._lines:
if line.product == product:
line.qty += qty
return
self._lines.append(Line(product, qty))
def __len__(self):
return len(self._lines)
def __iter__(self):
for line in self._lines:
yield line
@property
def subtotal(self):
return sum(line.cost for line in self._lines)Шаг 6. Первые заказы
По списку requests — что попросил каждый покупатель — соберите корзины. Сохраните в subtotals суммы корзин
по порядку, а в unknown — названия, которых нет в каталоге (такие позиции пропускайте, остальное в корзину
кладите).
Cart(catalog); для каждой позиции — try: cart.add(name, qty), except UnknownProductError: unknown.append(name).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
requests = [
[("Латте", 2), ("Круассан", 2), ("Латте", 1)],
[("Капучино", 1), ("Глинтвейн", 1), ("Чай", 2), ("Маффин", 1)],
[("Раф", 1), ("Какао", 1)],
]
subtotals = ...
unknown = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
requests = [
[("Латте", 2), ("Круассан", 2), ("Латте", 1)],
[("Капучино", 1), ("Глинтвейн", 1), ("Чай", 2), ("Маффин", 1)],
[("Раф", 1), ("Какао", 1)],
]
subtotals = []
unknown = []
for request in requests:
cart = Cart(catalog)
for name, qty in request:
try:
cart.add(name, qty)
except UnknownProductError:
unknown.append(name)
subtotals.append(cart.subtotal)Итог
print(f"в каталоге {len(catalog)} товаров")
for i, total in enumerate(subtotals, 1):
print(f"корзина {i}: {total} ₽")
print("нет в каталоге:", ", ".join(unknown))в каталоге 8 товаров корзина 1: 1000 ₽ корзина 2: 590 ₽ корзина 3: 450 ₽ нет в каталоге: Глинтвейн
Модель кассы готова: каталог из восьми товаров, три корзины на 1000, 590 и 450 ₽, а «Глинтвейна» в каталоге нет — касса не упала, а сообщила об этом исключением. В следующем уроке к этой модели добавятся скидки, оплата и чек.