Что такое декоратор
Декоратор — это функция, которая принимает другую функцию и расширяет её поведение без изменения исходного кода. Декораторы широко применяются для логирования, кэширования, проверки прав доступа, измерения времени выполнения и других сквозных задач. В Python декораторы используют синтаксис @decorator_name перед определением функции, что делает код чище и переиспользуемым.
Как это работает
Декоратор — это по сути функция высшего порядка: она принимает функцию как аргумент и возвращает новую функцию (или другой callable-объект), которая обычно оборачивает исходную. Внутри обёртки (wrapper) можно выполнять код до и после вызова исходной функции, изменять аргументы или результат, а также обрабатывать исключения.
Пример простейшего декоратора:
def my_decorator(func):
def wrapper():
print("До вызова функции")
func()
print("После вызова функции")
return wrapper
@my_decorator
def say_hello():
print("Привет!")
say_hello()Вывод:
До вызова функции
Привет!
После вызова функцииСинтаксис @my_decorator эквивалентен say_hello = my_decorator(say_hello). То есть декоратор применяется к функции сразу после её определения.
Примеры использования
Декораторы удобны для задач, которые повторяются во многих функциях. Рассмотрим несколько типичных примеров.
Логирование вызовов:
def log(func):
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__} с аргументами {args}")
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
add(2, 3) # Вызов add с аргументами (2, 3)Кэширование результатов:
def cache(func):
memo = {}
def wrapper(n):
if n not in memo:
memo[n] = func(n)
return memo[n]
return wrapper
@cache
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)Проверка прав доступа:
def require_admin(func):
def wrapper(user, *args, **kwargs):
if not user.is_admin:
raise PermissionError("Требуются права администратора")
return func(user, *args, **kwargs)
return wrapperПодводные камни
При использовании декораторов важно помнить о нескольких нюансах.
-
Потеря метаданных функции. Обычный декоратор заменяет исходную функцию на
wrapper, из-за чего теряются__name__,__doc__и другие атрибуты. Решение — использоватьfunctools.wraps:pythonfrom functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper -
Декораторы с аргументами. Если нужно передать декоратору параметры (например, уровень логирования), требуется дополнительный уровень вложенности:
pythondef repeat(times): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def say_hi(): print("Привет") -
Порядок применения. Несколько декораторов применяются снизу вверх, а выполняются сверху вниз. Например,
@aи@bэквивалентныa(b(func)).
Когда использовать
Декораторы стоит применять, когда нужно единообразно добавить поведение к множеству функций без дублирования кода. Они особенно полезны в веб-фреймворках (например, для маршрутизации), в ORM (для связей), в тестировании (для моков) и в аспектно-ориентированном программировании. Однако не стоит злоупотреблять: излишняя вложенность декораторов может усложнить чтение и отладку кода.
Коротко
- Декоратор — функция, принимающая функцию и возвращающая новую функцию с расширенным поведением.
- Синтаксис
@decorator— это сахар дляfunc = decorator(func). - Используйте
functools.wraps, чтобы сохранить метаданные исходной функции. - Декораторы с аргументами требуют тройной вложенности:
decorator(args) -> decorator -> wrapper.
