📜 Как показано в исходном материале
Исходная лекция уже использует аннотации типов и читаемые имена. Функция факториала выглядит так:
# из исходника
def factorial(n: int) -> int:
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5))
Этот код корректен и понятен, но не защищён от отрицательных чисел и не объясняет контракт функции.
❌ Что можно улучшить
- Нет проверки аргументов:
factorial(-1)уйдёт в бесконечную рекурсию. - Нет docstring — неясно, какие значения допустимы.
- Хвостовая рекурсия в исходнике показана как концепция, но в Python она не даёт преимуществ.
- Бинарный поиск в исходнике использует
elif/else; современный стиль предпочитает ранние возвраты.
✅ Рекомендуемый современный вариант
# modern_factorial.py
def factorial(n: int) -> int:
"""Возвращает факториал неотрицательного целого числа n."""
if n < 0:
raise ValueError("n must be non-negative")
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
# print(factorial(-1)) # ValueError
Что улучшилось:
- Guard clause сразу отсекает некорректные данные.
- Docstring описывает контракт функции.
- Ранний возврат уменьшает вложенность и упрощает чтение.
Современный бинарный поиск
# modern_binary_search.py
from typing import Optional
def binary_search(
arr: list[int],
target: int,
left: int,
right: int
) -> Optional[int]:
"""Возвращает индекс target в отсортированном списке arr или None."""
if left > right:
return None
mid = (left + right) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
return binary_search(arr, target, mid + 1, right)
return binary_search(arr, target, left, mid - 1)
Здесь убраны лишние elif/else: как только функция знает ответ, она сразу возвращает его.
Замена хвостовой рекурсии на итерацию
# modern_iterative_factorial.py
def factorial_iterative(n: int) -> int:
"""Итеративный факториал без риска переполнения стека."""
if n < 0:
raise ValueError("n must be non-negative")
accumulator = 1
while n > 1:
accumulator *= n
n -= 1
return accumulator
🆕 Современные альтернативы для разделения по типам
match-case. В более ранних версиях остаётся isinstance().
# match_dispatch.py
def describe(value) -> str:
match value:
case int():
return f"целое число {value}"
case str():
return f"строка длиной {len(value)}"
case list():
return f"список из {len(value)} элементов"
case _:
return "что-то другое"
print(describe(42))
print(describe("hello"))
print(describe([1, 2, 3]))
Мемоизация рекурсии
functools.lru_cache. Это не отменяет лимит стека, но сильно сокращает количество вычислений.
# lru_cache_demo.py
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100))
🕰️ Когда старый подход ещё можно встретить
Учебные материалы и простые скрипты часто пишут без guard clauses и docstrings. Это нормально для быстрых экспериментов, но в production-коде и домашних заданиях стоит добавлять проверки и документацию.