Какой паттерн соответствует верхнему уровню функции в параметризированном декораторе в Python
Что проверяют
Понимаете ли вы, что параметризованный декоратор состоит из трёх вложенных функций и какая из них за что отвечает. Следом обычно просят написать такой декоратор руками или спрашивают, чем @retry отличается от @retry().
Как отвечать
Верхний уровень реализует фабрику, точнее фабрику декораторов: функция принимает параметры и возвращает настоящий декоратор. Разложите по уровням:
- фабрика
retry(times): вызывается один раз, в момент определения функции, то есть обычно при импорте модуля. Параметры запоминаются в замыкании; - декоратор
decorator(func): получает целевую функцию и возвращает обёртку. Тоже срабатывает один раз; - обёртка
wrapper(*args, **kwargs): выполняется при каждом вызове и видит через замыкание иtimes, иfunc.
Запись @retry(times=3) равносильна func = retry(times=3)(func). Отсюда понятно, почему скобки обязательны: без них в times попадёт сама функция.
Покажите, что знаете детали:
functools.wraps: копирует в обёртку__name__,__doc__,__qualname__,__module__и добавляет__wrapped__. Без него ломаются логи, документация и интроспекция;- возвращаемое значение: обёртка должна делать
return func(*args, **kwargs), иначе декорированная функция начнёт возвращатьNone; - классовый вариант: параметры принимает
__init__, функцию принимает__call__. Роль фабрики тогда играет конструктор.
Можно добавить, что с паттерном «Декоратор» из GoF это связано по смыслу: обёртка добавляет поведение, не меняя исходный код. Но синтаксис @ сам по себе только сахар для вызова функции.
Где спотыкаются
Отвечают «декоратор» на вопрос про верхний уровень. Декоратор это средняя функция, верхняя его производит, поэтому правильное слово «фабрика».
Путают моменты выполнения. Код фабрики и декоратора работает при импорте, и если там тяжёлая инициализация или чтение конфига, это выстрелит не там, где ждали.
Забывают return в обёртке. Так выглядят многие учебные примеры с repeat, и функция, которая раньше возвращала результат, после декорирования тихо отдаёт None.