Какова связь между декоратором в Python и паттерном проектирования декоратор?
Что проверяют
Различаете ли вы синтаксис языка и паттерн проектирования, у которых совпало название. Сильный ответ звучит как «идея общая, механизм разный». Следом обычно просят написать декоратор с параметрами или показать классический паттерн на Python.
Как отвечать
Паттерн «Декоратор» из книги GoF: объект-обёртка реализует тот же интерфейс, что и исходный объект, хранит на него ссылку, делегирует вызовы и добавляет поведение до или после. Обёртки вкладываются друг в друга и навешиваются во время работы программы на конкретный экземпляр. В стандартной библиотеке так устроен ввод-вывод: BufferedReader оборачивает сырой FileIO и добавляет буферизацию, сохраняя интерфейс чтения байтов.
Декоратор в Python это синтаксис: запись @log над функцией равносильна f = log(f). Он срабатывает один раз, в момент определения функции, обычно при импорте модуля, и меняет функцию для всех вызовов сразу.
Связь и различия:
- общее: обёртка
wrapper(*args, **kwargs)сохраняет интерфейс вызова и добавляет поведение, не трогая исходный код, как и в паттерне; - объект применения: паттерн работает с объектами в рантайме, синтаксис работает с функциями и классами на этапе определения;
- свобода: Python-декоратор может вернуть что угодно.
@app.routeво Flask регистрирует функцию и возвращает её без обёртки,@propertyпревращает метод в дескриптор,@dataclassдописывает классу методы. К паттерну это уже не относится.
В PEP 318 авторы сами признают, что название совпадает с паттерном не полностью. И обязательно назовите functools.wraps: он переносит на обёртку __name__ и __doc__ и добавляет __wrapped__.
Где спотыкаются
Отвечают «Python-декоратор и есть реализация паттерна» и на этом останавливаются. Интервьюер спросит про @app.route, и ответ рассыпается.
Пишут обёртку без functools.wraps, и все декорированные функции в логах и трейсбеках называются wrapper.
Путают уровни вложенности у декоратора с параметрами: @retry(times=3) это вызов фабрики, которая возвращает декоратор, поэтому функций там три. Отсюда ошибки, когда один декоратор пытаются использовать и как @retry, и как @retry().