Каким ключевым словом в Python возвращается управление из функции-декоратора
Что проверяют
Короткий ответ: return. Но за ним проверяют, понимаете ли вы, что декоратор это функция, которая принимает функцию и возвращает новую. Настоящая проверка начинается со следующего вопроса: что вернёт обёртка и что станет с именем и документацией исходной функции.
Как отвечать
В декораторе return встречается дважды, и у каждого своя роль:
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"call {func.__name__}")
return func(*args, **kwargs)
return wrapper- внешний
return wrapper: отдаёт обёртку, которая заменит исходную функцию. Запись@log_callsнадdef fравносильнаf = log_calls(f); - внутренний
return func(...): возвращает вызывающему коду результат исходной функции. Без него любая обёрнутая функция начнёт возвращатьNone; - *`args
иkwargs`: обёртка принимает любые аргументы и передаёт их дальше, иначе декоратор сломается на первой функции с параметрами; functools.wraps: копирует__name__,__doc__,__module__и добавляет__wrapped__. Без него логи,help()и фреймворки, которые смотрят на имя функции, увидятwrapper.
Декоратор с параметрами добавляет третий уровень: retry(times) возвращает сам декоратор, а тот возвращает обёртку. Для корутин обёртка должна быть async def и делать return await func(*args, **kwargs). Иначе код «после вызова» выполнится раньше, чем корутина реально отработает, и замер времени или перехват исключений окажутся пустыми.
Где спотыкаются
Забывают return внутри обёртки. Декоратор исправно логирует вызов, а функция молча возвращает None, и баг всплывает далеко от места ошибки.
Пишут return wrapper() со скобками. Тогда обёртка вызывается один раз в момент декорирования, а на место функции встаёт её результат.
Путают return и yield. yield нужен в генераторах и в @contextlib.contextmanager, где он передаёт управление в тело блока with, но к возврату из декоратора отношения не имеет.