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

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

Из источника: docstrings, type hints, работа со словарями и рекурсия уже в современном стиле.

Современная практика: добавляем guard clauses, ранние возвраты, используем dict.get() вместо проверки in, заменяем хвостовую рекурсию на итерацию.

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

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

Исходная лекция уже использует аннотации типов и читаемые имена. Пример функции с docstring:

# из исходника
def greet(name):
    """
    Функция принимает имя и возвращает строку приветствия.
    
    :param name: Имя пользователя.
    :return: Приветственное сообщение.
    """
    return f"Hello, {name}!"

Пример замены значений в словаре:

# из исходника
data = {"x": 1, "y": 2, "z": 3}
number_to_word = {1: "один", 2: "два", 3: "три"}
for key in data:
    if data[key] in number_to_word:
        data[key] = number_to_word[data[key]]
print(data)

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

❌ Что можно улучшить

  • Нет аннотаций типов в учебных примерах.
  • Изменение исходного словаря вместо создания нового может привести к побочным эффектам.
  • Проверка data[key] in number_to_word менее удобна, чем number_to_word.get(...).
  • Рекурсивные функции не всегда защищены от отрицательных аргументов.

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

# modern_greet.py
def greet(name: str) -> str:
    """Возвращает приветственное сообщение для пользователя.

    :param name: Имя пользователя.
    :return: Строка приветствия.
    """
    return f"Hello, {name}!"

Современная замена значений в словаре:

# modern_translate.py
def translate_values(
    data: dict[str, int],
    mapping: dict[int, str]
) -> dict[str, str]:
    """Заменяет числовые значения словаря на строки по mapping."""
    return {
        key: mapping.get(value, str(value))
        for key, value in data.items()
    }


number_to_word = {1: "один", 2: "два", 3: "три"}
data = {"x": 1, "y": 2, "z": 3}
print(translate_values(data, number_to_word))

Что улучшилось:

  • Функция чистая — не изменяет исходный словарь.
  • Аннотации типов делают контракт явным.
  • mapping.get(value, str(value)) короче и безопаснее.

Современный факториал

# 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)

Замена хвостовой рекурсии на итерацию

# 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

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

Учебные материалы и простые скрипты часто пишут без аннотаций и guard clauses. Это нормально для быстрых экспериментов, но в production-коде и домашних заданиях стоит добавлять проверки и документацию.