Короткий ответ
Декоратор с параметрами реализуется как функция, принимающая аргументы конфигурации и возвращающая настоящий декоратор — функцию одного аргумента (саму декорируемую функцию), то есть добавляется дополнительный уровень вложенности функций.
Как это работает подробнее
@retry(times=3) сначала вызывает retry(times=3), которая должна вернуть обычный декоратор; этот декоратор возвращает wrapper, использующий захваченное через closure значение times при каждом вызове.
Пример кода
example-262.py
import functools, time
def retry(times=3, delay=0.1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exc = None
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exc = e
time.sleep(delay)
raise last_exc
return wrapper
return decorator
@retry(times=2, delay=0)
def flaky():
raise ValueError("временная ошибка")
# Проверка: flaky() поднимет ValueError только после 2 попыток, а не с первойЧто использовать на практике
- Практика. Паттерн применяется для настраиваемого поведения: число повторов, таймаут, разрешённые роли для проверки доступа. Важно пробрасывать
functools.wrapsна самом внутреннемwrapper.
Подводные камни
- Риски. Путают уровни вложенности и забывают один уровень функций; не учитывают, что аргументы декоратора вычисляются один раз при импорте модуля, а не при каждом вызове декорированной функции.