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

Мини-проект: оплата

Абстрактный PaymentMethod и три способа оплаты — наличные, карта и баллы. Заказ содержит способ оплаты (композиция), создаётся из строки методом класса, а касса проводит день оплат.

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

Касса «Кофейной лавки» принимает наличные, банковскую карту и баллы лояльности. Каждый способ оплаты по-своему проверяет, хватает ли денег, и по-своему пишет строку в чек. А заказу всё равно, чем платят: он содержит способ оплаты и просит его заплатить нужную сумму. В проекте собраны все три идеи модуля: абстрактный класс, альтернативный конструктор и композиция.

Шаг 1. Договор для способов оплаты

[ ]Упражнение 1Абстрактный PaymentMethod решено

Объявите абстрактный класс PaymentMethod. __init__(self, title) записывает название способа в title, str() возвращает это название. Абстрактный метод pay(amount) подклассы реализуют так: провести оплату и вернуть строку для чека, а если денег не хватает — выбросить ValueError и ничего не менять.

from abc import ABC, abstractmethod


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

    def __str__(self):
        ...

    def pay(self, amount):
        ...

Шаг 2. Три способа оплаты

[ ]Упражнение 2Наличные решено

Объявите подкласс Cash: Cash(1000) — покупатель дал 1000 ₽ (атрибут given), название — "наличные". pay(640) возвращает "наличные 1000 ₽, сдача 360 ₽", а если дали меньше суммы — ValueError("недостаточно наличных").

class Cash(PaymentMethod):
    def __init__(self, given):
        ...

    def pay(self, amount):
        ...

[ ]Упражнение 3Банковская карта решено

Объявите подкласс Card: Card("2200 1234 5678 9012", 2000) — номер карты (number) и остаток на счёте (balance), название — "карта *9012" (последние четыре цифры номера). pay(amount) списывает сумму с balance и возвращает "карта *9012: списано 1250 ₽"; если денег на счёте меньше суммы — ValueError("недостаточно средств на карте"), остаток не меняется.

class Card(PaymentMethod):
    def __init__(self, number, balance):
        ...

    def pay(self, amount):
        ...

[ ]Упражнение 4Баллы лояльности решено

Объявите подкласс LoyaltyPoints: LoyaltyPoints(500) — баллов на карте (points), 1 балл = 1 ₽, название — "баллы". pay(380) списывает баллы и возвращает "баллы: списано 380, осталось 120"; если баллов меньше суммы — ValueError("недостаточно баллов"), баллы не меняются.

class LoyaltyPoints(PaymentMethod):
    def __init__(self, points):
        ...

    def pay(self, amount):
        ...

Шаг 3. Заказ

[ ]Упражнение 5Заказ со способом оплаты решено

Объявите класс Order: Order(17, 640, method) — номер number, сумма total и способ оплаты method (любой PaymentMethod); у нового заказа paid = False.

  • pay() просит способ оплаты заплатить total, ставит paid = True и возвращает строку чека "заказ №17: наличные 1000 ₽, сдача 360 ₽". Если способ оплаты выбросил ValueError, исключение уходит дальше, а paid остаётся False.
  • Метод класса from_line(line, method) создаёт заказ из строки "17;640" — номер и сумма, оба целые.
class Order:
    def __init__(self, number, total, method):
        ...

    def pay(self):
        ...

    @classmethod
    def from_line(cls, line, method):
        ...

Шаг 4. День на кассе

[ ]Упражнение 6Оплаты за день решено

В lines — четыре заказа за утро, в methods — чем расплачивался каждый покупатель (первый и четвёртый — одной и той же картой card). Создайте заказы методом from_line, оплатите их по порядку и сохраните в receipts строки чеков оплаченных заказов, а в failed — номера заказов, оплата которых не прошла. Пройти два списка параллельно поможет встроенная zip(lines, methods): она выдаёт пары «первый с первым, второй со вторым…».

card = Card("2200 1234 5678 9012", 2000)
lines = ["17;1250", "18;640", "19;380", "20;1800"]
methods = [card, Cash(1000), LoyaltyPoints(500), card]
receipts = ...
failed = ...

Итог

[ ]Касса за утро
print("Касса за утро")
for line in receipts:
    print(" ", line)
print("не оплачены:", ", ".join(f"№{n}" for n in failed))
print(f"на карте *9012 осталось {card.balance} ₽")
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
Касса за утро
  заказ №17: карта *9012: списано 1250 ₽
  заказ №18: наличные 1000 ₽, сдача 360 ₽
  заказ №19: баллы: списано 380, осталось 120
не оплачены: №20
на карте *9012 осталось 750 ₽
Итог появится, когда решите все упражнения урока, — в нём ответы. Ячейку можно и выполнить.

Три заказа из четырёх оплачены. Заказ №20 не прошёл: та же карта уже заплатила за заказ №17, и на счёте осталось 750 ₽ — меньше 1800. Касса узнала об этом из ValueError, а не проверкой «какой это способ оплаты».

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

Обратите внимание, сколько кода касса знает о способах оплаты: ни одного isinstance. Новый способ — скажем, оплата по QR-коду — это ещё один подкласс PaymentMethod, а Order и цикл дня не меняются. Абстрактный класс гарантирует, что у нового способа будет pay, иначе его не создать.

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

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

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