⚖️ Старый и современный подход

⚡ Различия в двух словах

Старый/исходный подход: код из LMS работает корректно, но не содержит аннотаций типов и использует сообщения вместо исключений там, где логичнее прервать выполнение.

Новый подход: добавляем type hints, f-strings, guard clauses и пользовательские исключения. Код становится самодокументируемым и проще в поддержке.

Вывод: для учебных целей подходит любой рабочий вариант, но в реальных проектах предпочтительнее современный стиль.

📜 Как показано в исходном материале

Исходное решение задачи 3 из LMS (сокращено для наглядности):

import time
from datetime import datetime, timedelta

class Door:
    def __init__(self, code, max_attempts=3, block_minutes=15.0):
        self.__code = code
        self.__failed_attempts = 0
        self.__max_attempts = max_attempts
        self.__block_duration = timedelta(minutes=block_minutes)
        self.__block_until = None

    def unlock(self, code):
        if self.__is_blocked():
            self.__show_block_time()
            return False

        if self.__is_valid_code(code):
            print("Access granted.")
            self.__reset_attempts()
            return True
        else:
            print("Access denied.")
            self.__handle_failed_attempt()
            return False

    # ... остальные методы ...

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

❌ Почему этот подход можно улучшить

  • Нет аннотаций типов. Неясно, что code — строка, а block_minutes — число.
  • Лишние else. После return внутри if блок else не нужен — guard clause делает код плоским.
  • Сообщение вместо исключения. В задаче 4 требуется именно raise DoorBlockedError, потому что печать в консоль не даёт вызывающему коду шанс обработать ситуацию.
  • Меньше документации. Отсутствуют docstring и типизация, которые ускоряют чтение чужого кода.

✅ Рекомендуемый современный вариант

Та же логика, но с type hints, f-strings, guard clauses и пользовательским исключением:

import time
from datetime import datetime, timedelta


class DoorBlockedError(Exception):
    '''Raised when trying to access a blocked door.'''
    pass


class Door:
    def __init__(self, code: str, max_attempts: int = 3, block_minutes: float = 15.0) -> None:
        self.__code = code
        self.__failed_attempts = 0
        self.__max_attempts = max_attempts
        self.__block_duration = timedelta(minutes=block_minutes)
        self.__block_until = None

    def unlock(self, code: str) -> bool:
        if self.__is_blocked():
            raise DoorBlockedError(self.__remaining_block_time())

        if self.__is_valid_code(code):
            print("Access granted.")
            self.__reset_attempts()
            return True

        print("Access denied.")
        self.__handle_failed_attempt()
        return False

    # ... остальные методы ...
Преимущества:
  • Аннотации типов сразу показывают контракт метода.
  • Guard clauses уменьшают вложенность и упрощают чтение.
  • f-strings делают форматирование оставшегося времени коротким и понятным.
  • Исключение позволяет вызывающему коду реагировать на блокировку гибко.
Проверить по документации: рекомендации по стилю в PEP 8, по аннотациям типов — PEP 484.

🕰️ Когда старый подход ещё можно встретить

Код без type hints и с обилием print часто встречается в учебных материалах, тестовых скриптах и legacy-проектах, написанных до Python 3.6. Если вы видите такой код, не спешите всё переписывать: сначала убедитесь, что логика корректна, а затем постепенно добавляйте типизацию и тесты.