📝 Практические задания

⚡ Задания в двух словах

  • Задание 1 — онлайн-платёжные системы: абстрактный класс и два наследника.
  • Задание 2 — проверка платежей: пользовательское исключение и валидация.
  • Задание 3 — фигуры и площади: Shape, Circle, Rectangle.
  • Задание 4 — проверка размеров фигур: исключение InvalidSizeError.
  • Задание 5 ★ — итератор диапазона: __iter__ и __next__.
  • Задание 6 ★★ — умная дверь: своё исключение, счётчик попыток и блокировка по времени.

Задание 1. Онлайн-платёжные системы

Условие: создайте абстрактный класс PaymentProcessor с методом pay(amount). Реализуйте два класса:

  • PaypalPayment — печатает Paid {amount} via PayPal.
  • CreditCardPayment — печатает Paid {amount} via Credit Card.

Что должно получиться: можно создать список платёжных процессоров и вызвать pay() для каждого.

Подсказка: не забудьте наследовать PaymentProcessor от ABC.

Задание 2. Проверка платежей

Условие: доработайте систему из задания 1:

  • Создайте пользовательское исключение InvalidPaymentError.
  • В каждом платёжном классе метод pay(amount) должен проверять сумму:
  • Если сумма меньше или равна нулю, выбрасывать InvalidPaymentError.
  • Иначе проводить платёж.

Что должно получиться: отрицательная сумма вызывает InvalidPaymentError, который можно перехватить.

Подсказка: вынесите общую проверку суммы в метод или статический метод базового класса, чтобы не дублировать код.

Задание 3. Фигуры и площади

Условие: создайте абстрактный класс Shape с методом area(). Реализуйте:

  • Circle(radius) — площадь круга.
  • Rectangle(width, height) — площадь прямоугольника.

Что должно получиться:

shapes = [Circle(3), Rectangle(4, 5)]
for shape in shapes:
    print(f"Area: {shape.area():.2f}")

Подсказка: для числа π используйте math.pi.

Задание 4. Проверка размеров фигур

Условие: доработайте фигуры из задания 3:

  • Добавьте проверку в конструкторы Circle и Rectangle, чтобы значения были положительными.
  • Если передано отрицательное или нулевое значение, выбрасывайте пользовательское исключение InvalidSizeError.

Что должно получиться: при попытке создать Circle(-1) или Rectangle(0, 5) возникает InvalidSizeError.

Подсказка: чтобы InvalidSizeError можно было ловить и как ValueError, наследуйте его от ValueError.

Задание 5 ★. Итератор диапазона

Условие: создайте класс Countdown, который принимает число start и итерируется от start до 1 включительно.

for n in Countdown(5):
    print(n)  # 5 4 3 2 1

Что должно получиться: объект поддерживает протокол итератора.

Подсказка: реализуйте __iter__ и __next__. Не забудьте выбросить StopIteration.

Задание 6 ★★. Умная дверь с блокировкой

Условие: реализуйте класс Door — кодовый замок, который блокируется после нескольких неудачных попыток.

Исключение: DoorBlockedError — выбрасывается при обращении к заблокированной двери.

Атрибуты:

  • code — текущий код доступа;
  • failed_attempts — счётчик неудачных попыток;
  • max_attempts — максимум попыток (по умолчанию 3);
  • block_duration — длительность блокировки;
  • block_until — момент окончания блокировки.

Методы:

  • __init__(code, max_attempts=3, block_minutes=15.0) — инициализация двери;
  • unlock(code) — попытка открыть дверь;
  • change_code(old_code, new_code) — смена кода;
  • is_valid_code(code) — проверка правильности кода;
  • is_blocked() — проверка, заблокирована ли дверь;
  • remaining_block_time() — оставшееся время блокировки;
  • check_block() — проверка блокировки перед операцией;
  • handle_failed_attempt() — обработка неудачной попытки;
  • reset_attempts() — сброс счётчика попыток.

Логика работы:

  • Любой вызов unlock() или change_code() сначала проверяет блокировку. Если дверь заблокирована — сразу DoorBlockedError.
  • Правильный код → успех и сброс счётчика неудач.
  • Неправильный код → отказ и увеличение счётчика.
  • Счётчик достиг max_attempts → дверь блокируется на block_minutes минут.
  • change_code() требует правильный старый код; при ошибке код не меняется.

Что должно получиться:

door = Door("1234", max_attempts=3, block_minutes=15)

door.unlock("0000")   # Access denied (попытка 1 из 3)
door.unlock("1111")   # Access denied (попытка 2 из 3)
door.unlock("2222")   # Too many failed attempts — дверь заблокирована
door.unlock("1234")   # DoorBlockedError, даже с правильным кодом

Подсказка: для времени используйте datetime.now() и timedelta(minutes=...). Блокировка активна, пока block_until ещё не наступил.

Проверка понимания

Выберите один вариант ответа. После клика правильный ответ подсветится зелёным.

Вопрос 1

Что такое класс?

Вопрос 2

Что означает self?

Вопрос 3

Какой стиль имён переменных обычно рекомендует PEP 8?

Вопрос 4

Зачем использовать f-strings в современном Python?

Вопрос 5

Когда полезен ранний возврат из функции?

Вопрос 6

Что делает аннотация типа в Python?

Вопрос 7

Как лучше обрабатывать ожидаемую ошибку преобразования?