В КУРСЕ?

Разбираемся в теме

Декоратор Python сработал, а результат исчез: что должна сохранять обёртка?

Декоратор позволяет добавить поведение вокруг вызова функции: например, отметить начало выполнения. Но внешне успешный запуск ещё не означает, что исходная функция сохранила свой контракт. Обёртка может потерять результат, перестать принимать именованные аргументы или скрыть исключение. Разберём простой синхронный случай без классов, генераторов и асинхронного выполнения.

Различайте создание обёртки и её вызов

Когда выполняется определение функции с декоратором, созданный объект передаётся декоратору. Его результат связывается с именем функции. Поэтому запись с @trace можно понимать через преобразование rectangle = trace(rectangle), с небольшими различиями в промежуточном связывании имени. У обычного оборачивающего декоратора есть внешний уровень, принимающий функцию, и внутренняя функция, которую он возвращает. Действие во внешнем уровне происходит при декорировании. Действие внутри возвращённой обёртки происходит при её вызове. Сообщение о начале вычисления обычно нужно размещать именно во внутреннем уровне, иначе оно появится раньше самого расчёта.

Передавайте аргументы без потери смысла

Представим функцию rectangle с параметрами width и height, причём высота по умолчанию равна двум. Она возвращает произведение ширины и высоты. Корректная обёртка должна сохранить возможность вызвать её и с двумя позиционными числами, и с именованными аргументами, и только с шириной. Для общего случая внутренняя функция принимает *args и **kwargs, а затем передаёт их исходной функции через func(*args, **kwargs). Это не отменяет проверку аргументов самой исходной функцией. Если у неё нет нужного параметра, универсальная форма обёртки не делает такой аргумент допустимым.

Верните результат и не прячьте исключение

Строка вызова исходной функции без возврата результата заставит обычную обёртку вернуть None, когда её выполнение дойдёт до конца. Расчёт внутри при этом действительно произойдёт. Поэтому проверять нужно значение, полученное вызывающим кодом, а не только факт появления сообщения. В простом варианте подходит return func(*args, **kwargs). Если дополнительная задача состоит только в отметке вызова, не стоит попутно превращать любое исключение в ноль или пустую строку. Иначе ошибка станет похожа на нормальный ответ. Изменение обработки ошибок требует отдельного, явно сформулированного контракта.

Сохраняйте сведения о функции осознанно

У возвращённой внутренней функции будут собственные имя и строка документации. Для переноса обычных метаданных применяют functools.wraps к обёртке с указанием исходной функции. Он также предоставляет атрибут __wrapped__, указывающий на оборачиваемый объект. Это помогает инструментам интроспекции, но не исправляет потерянный return и не проверяет правильность передачи аргументов. Метаданные и поведение нужно проверять отдельно. В учебном журнале достаточно фиксировать сам факт вызова: записывать реальные пароли, токены или персональные данные из аргументов для этого не требуется.

Попробуйте на практике

Напишите декоратор trace для учебной функции rectangle, которая умножает ширину на высоту и отклоняет отрицательные размеры через ValueError.

  1. Создайте функцию с высотой по умолчанию два. До декорирования проверьте: размеры три и четыре дают двенадцать, одна ширина пять даёт десять.
  2. Во внешнем уровне trace добавьте в список событий отметку декорирования. Во внутренней обёртке добавляйте отметку вызова, передавайте все аргументы и возвращайте результат.
  3. Используйте functools.wraps и примените декоратор. Проверьте список сразу после определения функции: вычисление площади ещё не должно выполняться.
  4. Вызовите функцию с позиционными аргументами, с width=3 и height=4, затем только с числом пять.
  5. Проверьте имя rectangle, сохранение строки документации и передачу ValueError при отрицательном размере.

Как проверить результат. Результаты трёх корректных вызовов равны 12, 12 и 10. До них есть только отметка декорирования. Ошибочный четвёртый вызов добавляет отметку вызова и передаёт ValueError наружу. Имя и документация относятся к исходной функции.

Частые вопросы

Обязан ли любой декоратор возвращать внутреннюю функцию?

Нет. Здесь рассмотрен распространённый вариант обёртки. В общем механизме имя получает возвращённый декоратором объект, поэтому его поведение определяется реализацией.

Почему wraps не устранил возвращаемый None?

Он занимается метаданными обёртки. Возвращение результата обеспечивается её собственным кодом, поэтому наличие return нужно проверить отдельно.

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов