Декораторы и генераторы в Python
Что такое декоратор, зачем он нужен, и почему код внутри генератора выполняется не при создании, а при итерации?
Ответить самому
Сначала сформулируйте ответ как на собеседовании, затем откройте разбор и оцените себя.
Короткий ответ
Декоратор принимает функцию и возвращает новую функцию-обертку. Генераторная функция при вызове возвращает generator object; ее тело начинает выполняться только при next(), цикле или list(generator).
Полный разбор
Декоратор - это обычная функция высшего порядка. Синтаксис @decorator примерно равен func = decorator(func). Обертка может логировать вызовы, проверять права, кешировать результат, менять аргументы или выполнять код до/после исходной функции. Для сохранения имени и docstring обычно используют functools.wraps.
Генераторная функция содержит yield. При вызове она не выполняет тело сразу, а возвращает generator object. Код внутри начнет выполняться, когда кто-то запросит следующий элемент: next(g), for, list(g). После каждого yield состояние функции сохраняется до следующего шага.
Поэтому принты до/после yield появляются в момент итерации, а не в момент создания генератора. Это часто спрашивают, чтобы проверить понимание lazy evaluation.
Теория
Декораторы меняют вызываемый объект, генераторы лениво производят значения и сохраняют состояние между итерациями.
Типичные ошибки
- Думать, что генератор выполняется при вызове функции.
- Писать декоратор без `*args, **kwargs` и ломать сигнатуру вызова.
- Забывать `functools.wraps` в production-декораторах.