Полное руководство по декораторам в Python, Часть 1: Регистрация функции

Декораторы в Python

Одной из отличительных черт фреймворка Flask является использование декораторов для популярных прикладных задач вроде определения маршрутов и обработки ошибок. Декораторы придают вашему коду лаконичную и понятную структуру. По этой причине большинство расширений Flask и многие другие пакеты в Python следуют одному и тому же шаблону и раскрывают основные части своей функциональности через декораторы.

Содержание статьи

С данной статьей мы начнем серию подробных публикаций о различных способах включения настраиваемых декораторов в приложения на Python. В первой части будет показано, как создавать простые декораторы, которые регистрируют функции как обратные вызовы для событий, специфичных для приложения.

Ниже представлен список статей данного курса о декораторах в Python:

Регистрация функции с помощью декораторов в Python

Самый простой тип декоратора используется для регистрации функции в качестве обработчика события. Данный шаблон очень распространен в приложениях на Python, так как он позволяет двум или более подсистемам взаимодействовать, не зная ничего друг о друге. Данный процесс известен как «несвязанный» дизайн.

Декоратор регистрации функции, у которого нет аргументов, обладает следующей структурой:

В данном примере request_logger является названием декоратора, который определен как стандартная функция Python. Выше было сказано, что данный декоратор не принимает аргументов, однако вы могли заметить, что здесь все-таки есть один аргумент под названием f.

При работе с декораторами имплементацию требуется делать отдельно (как показано выше) от использования. Чтобы помочь получить полную картину, посмотрите, как данный декоратор будет использоваться ниже:

Как видите, при использовании, у декоратора нет аргументов, но функция, которая имплементирует декоратор все-таки принимает аргумент. Данный аргумент действительно запрашивается, потому что функция декоратора вызывается Python «не напрямую» каждый раз при использовании декоратора, а Python передает функцию декоратора (функция log_a_request) из примера выше в качестве аргумента.

Эффект, который декоратор оказывает на декоратор функции можно лучше описать с помощью короткого фрагмента кода, через который можно добиться того же эффекта, но без использования декораторов:

Используя данный фрагмент как руководство, можно увидеть, что функция декоратора вызывается с декорированной функцией в качестве аргумента, а возвращаемое значение является функцией, которая занимает место оригинальной функции, которая декорировалась. Это позволяет продвинутым декораторам предоставлять альтернативную функцию для замены декорированной функции.

Более простые декораторы, которые будут описаны в данной главе, в этом не нуждаются, поэтому они оканчиваются просто на return f, чтобы сохранить оригинальную функцию нетронутой.

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

Данный декоратор регистрирует одну или несколько функций для работы в качестве регистраторов запросов. Единственное, что делает декоратор, это добавляет декорированную функцию в список:

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

Такое использование декоратора приведет к тому, что функция декоратора request_logger(), определенная выше, будет выполняться с функцией log_a_request, переданной в качестве аргумента f. Затем функция декоратора сохранит ссылку на эту функцию в глобальном списке all_request_loggers. Если есть другие функции, декорированные данным декоратором, все они будут добавлены в список all_request_loggers.

Последняя часть данной реализации является вызов всех функций, зарегистрированных в качестве регистраторов запросов, когда происходит событие (в данном случае получение запроса). Это можно сделать с помощью простого цикла for в отдельной функции:

Далее дана полная имплементация этого примера, использующая простое Flask приложение:

Вам может стать интересно, к чему все эти усилия, если гораздо проще вызвать функцию log_a_request() напрямую из функции представления index(). В некоторых случаях, особенно в более простых приложениях, это может быть хорошим решением. Однако использование декоратора позволяет приложению отделить функцию представления от функции или функций, зарегистрированных для регистрации запросов, что является хорошей практикой проектирования.

В приведенном выше примере функции представления index() не нужно знать о функциях логирования запросов и о их количестве и есть ли они вообще. Вы можете зарегистрировать различные функции логирования в деплое на этапе продакшена, а не при локальной разработке. Такой дизайн сохраняет код вашего приложения чистым и независимым от того, как логировать запрос.

В реальном приложении, функции декоратора будут находиться в собственном модуле, отдельно от других модулей или пакетов приложения. Любой модуль, которому необходимо зарегистрировать логирование запросов, будет импортировать декоратор request_logger и использовать его. Точно так же в любой части приложения, где требуется логировать запрос, можно импортировать и вызывать функцию invoke_request_loggers().

Шаблон проектирования Наблюдатель (Observer)

Вы могли заметить, что идеи представленные в данной статье, очень похожи на шаблон проектирования Наблюдатель, или Observer. Фактически декораторы в Python, используемые в манере, описанной выше, являются хорошим способом имплементации данного шаблона.

Шаблон Наблюдатель можно использовать в ряде случаев. К примеру:

  • В игре можно зарегистрировать обработчики событий, которые вызываются при столкновении двух спрайтов;
  • В прикладном приложении можно зарегистрировать обработчики обновления фона, которые вызываются, когда приложение бездействует;
  • В приложении командной строки можно зарегистрировать функции обработчика ошибок, которые вызываются для очистки после возникновения непредвиденных ошибок;
  • Говоря о веб-приложениях, можно привести несколько примеров из Flask. Декораторы route, before_request, after_request и teardown_request используют данный шаблон. Данные декораторы более сложные, чем тот, что был показан выше. Некоторые из них принимают аргументы, а другие подстраивают свое поведение под приложение и то, какие декорированные функции возвращаются. Эти более сложные аспекты будут рассмотрены в будущих статьях данного курса.

Заключение

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