Декораторы в Python: от основ до продвинутого уровня
Ключевые тезисы
- Декоратор — это паттерн программирования, позволяющий добавлять новый функционал к функции, не изменяя её исходный код.
- Синтаксис
@decorator— это синтаксический сахар дляmyfunc = decorator(myfunc). - Декоратор может быть функцией или классом, но чаще всего это функция.
- Важные аспекты: работа с параметрами функции, декораторы с собственными параметрами, сохранение метаданных (
@wraps) и поддержка асинхронных функций.
Основное содержание
Что такое декоратор?
Декоратор — это паттерн программирования, который позволяет добавлять новый функционал или обязанности к функции, не изменяя саму функцию. Дополнительный код выполняется до или после вызова основной функции.
Примеры использования:
- Подсчёт времени исполнения функции.
- Логирование параметров вызова.
- Повторный вызов функции в случае ошибки (retry).
- Ограничение частоты вызовов (rate limiting).
Скелет декоратора (функция)
Базовый декоратор — это функция, которая принимает другую функцию (callable-объект) и возвращает новую функцию-обёртку.
from typing import Callable
def decorator(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
# Код, выполняемый ДО вызова функции
result = func(*args, **kwargs) # Вызов исходной функции
# Код, выполняемый ПОСЛЕ вызова функции
return result
return wrapper
Декоратор с параметрами функции
Чтобы декоратор работал с любыми функциями (с аргументами или без), внутренняя обёртка wrapper должна принимать *args и **kwargs и передавать их в исходную функцию.
Декоратор с собственными параметрами
Если декоратору нужно передать параметр (например, лимит вызовов), добавляется ещё один уровень вложенности — функция-фабрика.
def limit_calls(limit: int):
def outer_wrapper(func: Callable) -> Callable:
def inner_wrapper(*args, **kwargs):
nonlocal limit # Важно: объявление nonlocal для изменения переменной внешней функции
if limit == 0:
raise Exception("Нельзя вызвать функцию")
limit -= 1
return func(*args, **kwargs)
return inner_wrapper
return outer_wrapper
Сохранение метаданных функции
Применение декоратора заменяет метаданные исходной функции (имя, docstring) на метаданные обёртки. Чтобы сохранить их, используется декоратор wraps из модуля functools.
from functools import wraps
def decorator(func: Callable) -> Callable:
@wraps(func) # Сохраняет __name__, __doc__ и другие метаданные
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Декоратор для асинхронных функций
Для оборачивания асинхронных функций (корутин) обёртка wrapper должна быть объявлена как async def, а вызов функции — через await.
from typing import Coroutine
def async_decorator(func: Coroutine) -> Callable:
async def wrapper(*args, **kwargs):
# Асинхронный код до вызова
result = await func(*args, **kwargs) # Асинхронный вызов
# Асинхронный код после вызова
return result
return wrapper
Полезные встроенные и модульные декораторы
@lru_cache(изfunctools): Кэширует результаты вызовов функции, что полезно для тяжёлых вычислений.@contextmanager(изcontextlib): Позволяет создать контекстный менеджер на основе функции-генератора (для использования сwith).
Примеры/кейсы
- Декоратор для логирования вызовов: Перед вызовом функции печатает её имя и переданные аргументы.
- Декоратор для замера времени: Фиксирует время до и после выполнения функции, вычисляет и выводит разницу.
Выводы
- Понимание принципа работы декораторов (
myfunc = decorator(myfunc)) — ключ к решению любых задач на эту тему. - Важно помнить о сохранении метаданных (
@wraps) и корректной обработке параметров (*args, **kwargs). - Умение писать декораторы для асинхронного кода и декораторы с параметрами отличает продвинутого разработчика.
- Декораторы широко применяются в реальных проектах (веб-фреймворки, логирование, кэширование, валидация), поэтому их глубокое понимание критически важно.