Что такое декоратор?
Декоратор — это функция, которая принимает другую функцию и возвращает новую, «обёрнутую» версию.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("До вызова")
result = func(*args, **kwargs)
print("После вызова")
return result
return wrapper
@my_decorator
def say_hello(name: str) -> str:
return f"Привет, {name}!"
Символ @my_decorator — это синтаксический сахар для say_hello = my_decorator(say_hello).
Проблема без functools.wraps
Без functools.wraps обёртка «ворует» метаданные исходной функции:
print(say_hello.__name__) # wrapper — неправильно!
print(say_hello.__doc__) # None
Используйте @functools.wraps(func):
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("До вызова")
return func(*args, **kwargs)
return wrapper
Теперь say_hello.__name__ вернёт "say_hello".
Декоратор с параметрами
Если декоратор принимает аргументы, нужен ещё один уровень вложенности:
def repeat(n: int):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet():
print("Привет!")
greet() # Привет! Привет! Привет!
Практичные примеры
Таймер
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__}: {elapsed:.4f}s")
return result
return wrapper
Кеширование (LRU Cache)
Python предоставляет встроенный декоратор @functools.lru_cache:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
Декоратор-класс
Декоратором может быть класс, реализующий __call__:
class Retry:
def __init__(self, attempts: int = 3):
self.attempts = attempts
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(self.attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if i == self.attempts - 1:
raise
return wrapper
@Retry(attempts=5)
def flaky_request():
...
Порядок применения нескольких декораторов
Декораторы применяются снизу вверх:
@decorator_a
@decorator_b
def foo():
pass
# Эквивалентно: decorator_a(decorator_b(foo))
Итог
| Паттерн | Когда использовать |
|---|---|
@functools.wraps | Всегда — сохраняет метаданные |
| Декоратор с аргументами | Когда нужна конфигурация |
@lru_cache | Рекурсия / дорогие вычисления |
| Декоратор-класс | Когда нужно хранить состояние |
Декораторы — мощный инструмент для разделения обязанностей: логирование, кеширование, ретраи и авторизация не должны загрязнять бизнес-логику.