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

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

Старый: функции без аннотаций, ручные фабрики замыканий, изменяемые значения по умолчанию.

Новый: аннотации типов, functools.partial, явный стиль и избегание изменяемых значений по умолчанию.

Вывод: современный код проще читать и безопаснее поддерживать.

📜 Старый подход: без аннотаций типов

def multiplier(factor):
    def multiply(number):
        return number * factor
    return multiply


double = multiplier(2)
print(double(5))

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

✅ Современный вариант

def multiplier(factor: float):
    def multiply(number: float) -> float:
        return number * factor
    return multiply


double = multiplier(2)
print(double(5))  # 10.0

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

Преимущества:
  • Меньше неожиданных ошибок при передаче аргументов.
  • Лучше работают type hints, mypy и IDE.
  • Код самодокументируемый.

📜 Ручная фабрика для простых случаев

def make_adder(value):
    def adder(x):
        return x + value
    return adder


add_five = make_adder(5)
print(add_five(3))  # 8

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

✅ Современный вариант: functools.partial

from functools import partial


def add(a: float, b: float) -> float:
    return a + b


add_five = partial(add, 5)
print(add_five(3))  # 8

Что улучшилось: встроенная функция functools.partial делает намерение очевидным и не требует вложенных функций для простых сценариев.

Проверить по документации: functools.partial создаёт вызываемый объект, а не обычную функцию. Подробнее — в документации.

📜 Изменяемые значения по умолчанию

def append_event(event, events=[]):
    events.append(event)
    return events


print(append_event("login"))      # ['login']
print(append_event("logout"))     # ['login', 'logout']  # сюрприз!

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

✅ Современный вариант: None вместо изменяемого значения

def append_event(event: str, events: list[str] | None = None) -> list[str]:
    if events is None:
        events = []
    events.append(event)
    return events


print(append_event("login"))   # ['login']
print(append_event("logout"))  # ['logout']

Что улучшилось: каждый вызов получает свой независимый список. Это соответствует рекомендации PEP 8.

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

Учебные материалы и legacy-код могут не использовать аннотации типов или functools.partial. Это нормально, но в новых проектах рекомендуется использовать современный стиль. Аннотации str | None требуют Python 3.10+.