📜 Как показано в исходном материале
Исходное решение задачи 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 делают форматирование оставшегося времени коротким и понятным.
- Исключение позволяет вызывающему коду реагировать на блокировку гибко.
🕰️ Когда старый подход ещё можно встретить
Код без type hints и с обилием print часто встречается в учебных материалах, тестовых скриптах и legacy-проектах, написанных до Python 3.6. Если вы видите такой код, не спешите всё переписывать: сначала убедитесь, что логика корректна, а затем постепенно добавляйте типизацию и тесты.