Декоратор(Decorator)

Декоратор(Decorator)

KarmanovichDev

Декоратор, или "обёртка" - паттерн(шаблон) проектирования, который позволяет динамически добавлять объектам требуемое поведение, не изменяя изначальный объект.


Несколько правил при работе с декораторами:

  • Декоратор должен повторять интерфейс того объекта, который декорирует.
  • Клиентский код не должен меняться(зависеть) от декораторов(есть исключения, когда в python их использование нарушает это правило, но как компромисс мы пользуемся этим функционалом для удобства).
  • Можно расширять поведение одного и того же объекта с помощью нескольких декораторов(комбинирование).


Начнём с самого простого способа реализации декораторов на python:

Проверим соблюдения правил для объекта wrapper. Именно им будет пользоваться клиентский код, object_logger это способ получения обёртываемого объекта:

  • Из-за сигнатуры параметров wrapper на вход, а именно *args, **kwargs наша функция обёртка становится обёрткой общего назначения. Это значит, что она может быть использована для любой функции или метода, независимо от входных параметров. Учитывая предыдущее предложение, можно с уверенностью сказать: наш декоратор может быть использован с любыми функциями и методами, не нарушая их интерфейс взаимодействия.
  • Раз мы не нарушаем интерфейс декорируемого объекта, то, значит, и клиентское взаимодействие так же остаётся неизменным. Клиентский код не знает, что на самом деле он вызывает функцию обёртку, которая обладает своей логикой в добавление к изначальному объекту.
  • Используя наш декоратор для объекта, нам ничего не мешает использовать и другие декораторы.
lru_cache - декоратор для кеширования ответов. Если вызвать объект с одними и теми же аргументами дважды, то на второй раз вернётся уже сохранённый ответ из оперативной памяти, без повторного вычисления. Проще говоря, функция sum_ даже не будет вызвана повторно.

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

Как думаете, что-то изменилось? Я отвечу вам, да! Из-за того, что lru_cache находится "выше" чем object_logger он будет выполнен раньше, и закеширует ответ от функции sum_(На самом деле он закеширует ответ от object_logger, который получит его от sum_). При повторном вызове функции sum_ с такими же аргументами, первым в дело вступил lru_cache. Он найдёт сохранённый ответ для такого набора аргументов, и вернёт его, не вызвав следующий объект(object_logger в данном случае), а значит, и не будет повторного логирования. Поэтому при использовании нескольких декораторов сразу, будьте внимательны.


Думаю, стало понятнее, как создать простой декоратор. А что, есть сложный? Почти... Есть возможность создать декоратор с параметрами. Это нужно для того, что бы от использования к использованию можно было управлять(менять) поведением декоратора:

Наш новый декоратор засекает время выполнения задекорированного им объекта. Если это время превышает заранее установленный нами лимит, то logger оставит об этом информацию в консоли(ну или где-то ещё в зависимости от его конфигурации).


Думаю, пора разобрать, как же работает этот механизм с @(синтаксический сахар). Вообще, если посмотреть на сам декоратор time_cheker, то всё станет понятно:

  1. time_cheker принимает аргумент seconds и возвращает пользователю decorator
  2. decorator принимает func и возвращает пользователю wrapper
  3. Клиент не знает, что сейчас будет работать с wrapper, для него это всё та же функция sum_. Он передаёт в неё аргументы, а на самом деле во wrapper, который засекает время начала, вызывает искомую функцию sum_(сохраняя её результат), проверяет время выполнения, и возвращает результат работы sum_ клиенту.

Давайте проделаем эти шаги сами(без @):

Как видите, ничего страшного за собой не скрывает синтаксис с использованием @, но добавляет нам удобства в использовании.


Схема паттерна(UML):


Дополнительная информация во второй части статьи:

  1. Почему на деле примеры с использованием @ не совсем правильное использование паттерна декоратор. И как же всё-таки правильно применять этот паттерн(python decorator(@) и pattern decorator это немного разные вещи).
  2. Создание декораторов через классы.
  3. Покажу, когда мы пренебрегаем правилом, что наш код, не должен зависеть от декораторов, для удобства.

Report Page