Как написать декоратор с параметрами и зачем нужен functools.wraps?
- Почему после декоратора у функции другое имя и пропала документация?
Декоратор с параметрами — фабрика декораторов: функция принимает параметры и возвращает обычный декоратор, т. е. три уровня вложенности. functools.wraps копирует в обёртку __name__, __doc__, __module__, __qualname__ и ставит __wrapped__ — иначе логи, документация, pytest и фреймворки видят «wrapper».
- @retry(times=3) ≡ func = retry(times=3)(func)
- __wrapped__ позволяет добраться до исходной функции (inspect.signature его учитывает)
- Для async-функций обёртка тоже должна быть async
А ещё Декоратор-класс — когда нужно состояние (счётчик, кэш)
Подробно
Уровни: retry(times=5) возвращает decorator; decorator(fetch) возвращает wrapper; wrapper выполняется при каждом вызове. Параметры times и delay доступны в обёртке через замыкание.
Без wraps: fetch.__name__ == "wrapper", docstring потерян, FastAPI и Click, читающие сигнатуру и имя, работают неправильно, в логах и профилях все функции называются одинаково.
Декоратор, работающий и со скобками, и без: проверяют, передан ли первым аргументом вызываемый объект — но это усложнение; проще всегда писать со скобками.
Async: для корутин нужна async def wrapper с await func(...); универсальный декоратор проверяет inspect.iscoroutinefunction(func).
Пример
import functools, time
def retry(times=3, delay=0.5):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception:
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5)
def fetch(url): ...Что хотят услышать
- Три уровня и роль замыкания.
- Что делает wraps и зачем.
- Нюанс с async-функциями.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Декоратор с параметрами — фабрика декораторов; wraps переносит в обёртку имя, docstring и __wrapped__
- ✗@retry(times=3) эквивалентно retry(func, times=3); functools.wraps нужен, чтобы обёртка выполнялась быстрее
- ✗Декоратор с параметрами пишется с одним уровнем вложенности
- ✗wraps запрещает повторно декорировать функцию
Разбор темы
- Функции — аргументы, области видимости, замыкания и декораторы
Как передаются аргументы, *args и **kwargs, LEGB, ловушка изменяемого значения по умолчанию, замыкания и позднее связывание, декораторы и functools.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.