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

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

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

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

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

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

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