Нашли ошибку?
Скачать чистый
мини-проект≈ 25 минупражнения: 0 из 6

Мини-проект: скидки

Иерархия скидок: базовый Discount и подклассы PercentDiscount, FixedDiscount и BuyNGetOne. Касса применяет любую скидку одним вызовом и выбирает самую выгодную.

Python запустится при первом выполнении

У «Кофейной лавки» три вида акций: процент от суммы, фиксированная сумма и «каждый n-й напиток в подарок». Касса должна уметь применить любую из них — и выбрать для покупателя самую выгодную. Если писать это цепочкой if, каждая новая акция будет требовать правки кассы. Вместо этого сделаем иерархию классов: у всех скидок один метод amount(cart) — сколько рублей скидка даёт для корзины, — а касса просто вызывает его.

Что у нас уже есть

Товар и корзина из прошлых модулей: subtotal — сумма корзины без скидок.

[ ]Товар и корзина
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __repr__(self):
        return f"Product({self.name!r}, {self.price})"


class Cart:
    def __init__(self, items=None):
        self.items = list(items) if items is not None else []

    def add(self, product):
        self.items.append(product)

    @property
    def subtotal(self):
        return sum(p.price for p in self.items)


cart = Cart([Product("Латте", 220), Product("Круассан", 140)])
print(cart.items, cart.subtotal)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[Product('Латте', 220), Product('Круассан', 140)] 360

Шаг 1. Базовая скидка

[ ]Упражнение 1Класс Discount решено

Допишите класс Discount: Discount("без скидки") хранит название акции в атрибуте title, str(discount) возвращает это название, а метод amount(cart) возвращает 0 — базовая «скидка» ничего не скидывает. Подклассы переопределят amount.

class Discount:
    def __init__(self, title):
        ...

    def __str__(self):
        ...

    def amount(self, cart):
        ...

Шаг 2. Процент и фиксированная сумма

Подклассы наследуют от Discount всё, что есть в шаге 1, и переопределяют только amount. Название акции каждый подкласс собирает сам и передаёт в __init__ базового класса.

[ ]Упражнение 2Процент от суммы решено

Объявите подкласс PercentDiscount: PercentDiscount(10) — скидка 10 %, процент хранится в атрибуте percent, название — "скидка 10 %". amount(cart) — процент от cart.subtotal, округлённый до целых.

class PercentDiscount(Discount):
    def __init__(self, percent):
        ...

    def amount(self, cart):
        ...

[ ]Упражнение 3Фиксированная сумма решено

Объявите подкласс FixedDiscount: FixedDiscount(150) — минус 150 ₽, сумма в атрибуте value, название — "минус 150 ₽". Скидка не может быть больше суммы корзины: для корзины на 100 ₽ amount — 100.

class FixedDiscount(Discount):
    def __init__(self, value):
        ...

    def amount(self, cart):
        ...

Шаг 3. Каждый n-й в подарок

[ ]Упражнение 4Каждый n-й бесплатно решено

Объявите подкласс BuyNGetOne: BuyNGetOne("Латте", 3) — каждый третий латте в подарок, название — "каждый 3-й Латте в подарок", атрибуты name и n. amount(cart): сколько товаров с таким названием в корзине, столько раз по n целиком — столько бесплатных; скидка — их число, умноженное на цену этого товара. Нет товара в корзине — 0.

class BuyNGetOne(Discount):
    def __init__(self, name, n):
        ...

    def amount(self, cart):
        ...

Шаг 4. Касса

[ ]Упражнение 5К оплате и лучшая акция решено

Напишите две функции. to_pay(cart, discount) — сумма к оплате: subtotal корзины минус скидка. best_discount(cart, discounts) — та скидка из списка, которая для этой корзины даёт больше всего рублей. Ни одна функция не должна проверять класс скидки.

def to_pay(cart, discount):
    ...


def best_discount(cart, discounts):
    ...

Шаг 5. Корзина Анны

[ ]Упражнение 6Что выгоднее Анне решено

В корзине Анны три латте, два круассана и чай. Для трёх акций из списка offers сохраните в prices словарь «название акции → сумма к оплате», а в best — название самой выгодной акции.

anna = Cart([Product("Латте", 220)] * 3 + [Product("Круассан", 140)] * 2 + [Product("Чай", 120)])
offers = [PercentDiscount(10), FixedDiscount(150), BuyNGetOne("Латте", 3)]
prices = ...
best = ...

Итог

[ ]Акции для корзины Анны
print(f"корзина Анны без скидки: {anna.subtotal} ₽")
for offer in offers:
    print(f"{offer}: скидка {offer.amount(anna)} ₽, к оплате {to_pay(anna, offer)} ₽")
print("лучшая акция:", best)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
корзина Анны без скидки: 1060 ₽
скидка 10 %: скидка 106 ₽, к оплате 954 ₽
минус 150 ₽: скидка 150 ₽, к оплате 910 ₽
каждый 3-й Латте в подарок: скидка 220 ₽, к оплате 840 ₽
лучшая акция: каждый 3-й Латте в подарок
Итог появится, когда решите все упражнения урока, — в нём ответы. Ячейку можно и выполнить.

Без скидки корзина Анны стоит 1060 ₽. Скидка 10 % даёт 106 ₽, «минус 150 ₽» — 150 ₽, а третий латте в подарок — 220 ₽: эта акция выгоднее всех, Анна платит 840 ₽.

Выводы с ответами появятся, когда решите все упражнения урока.

Посмотрите на функции кассы: в них нет ни одного isinstance. Новая акция — скажем, «скидка на выпечку» — это новый подкласс с методом amount, а to_pay и best_discount не меняются. Но базовый Discount пока можно создать и «забыть» переопределить в подклассе amount — касса молча посчитает скидку 0. Как запретить такое — в следующем модуле.

Практика по модулю

Задачи по ООП в Python для самостоятельной практики появятся в разделе «Задачи → ООП в Python».

Нашли ошибку?