Декораторы появились в Python как синтаксический сахар с версии 2.4, чтобы облегчить работу с функциями высшего порядка — такими, которые принимают или возвращают другие функции. Эволюция подходов к расширению функциональности функций привела к формату лаконичных и выразительных средств — аннотаций через @decorator.
В крупных проектах часто требуется модифицировать или оборачивать функции какой-либо дополнительной логикой: логирование, проверка доступа, кеширование, измерение времени работы. Без декораторов приходилось вручную вызывать обёртывающие функции, что раздувало код.
Декораторы позволяют выносить повторяемые аспекты в отдельные обёртки, тем самым повышая читаемость и переиспользуемость кода. С помощью @decorator можно элегантно добавлять функциональность к функциям и методам:
Пример кода:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Выведет, сколько заняло выполнение
Ключевые особенности:
Могут ли декораторы менять сигнатуру оборачиваемой функции?
Часто ошибочно считают, что сигнатура функции декоратором не меняется. На самом деле без использования модуля functools.wraps метаинформация исчезает, что может вызвать неожиданные ошибки в автодокументации или introspection.
Пример кода:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
Могут ли декораторы быть параметризированы?
Часто отвечают, что нельзя. На самом деле возможно создать параметризованный декоратор через дополнительный уровень вложенности — функцию, возвращающую декоратор.
Пример кода:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
Можно ли применять несколько декораторов к одной функции? Какой будет порядок исполнения?
Часто ошибочно полагают, что порядок не важен или он совпадает с порядком размещения декораторов в коде. На самом деле сначала применяется самый нижний декоратор, затем следующий и так далее вверх.
Пример кода:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
Логирование времени работы добавлено к 10 функциям вручную, копированием-кодированием.
Плюсы:
Минусы:
Вся логика тайминга вынесена в декоратор, все функции, где нужна эта метрика, обёрнуты декоратором @timing_decorator.
Плюсы:
Минусы:
functools.wraps, если не внимателен;