Декораторы для самых маленьких (Python)

от автора

Всем привет, меня зовут Аббакумов Валерий.

Я Python разработчик, в основном занимаюсь бэкэндом веб приложений. Хочу написать серию статей для начинающих разработчиков. Посты будут трех уровнях сложности (от меньшей к большей) на разные аспекты языка, с которыми сложно справиться обывателю.

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

Я прикреплю ссылки на смежные статьи (тоже хороший материал, но ИМХО в них либо странная подача либо некоторая неполнота), мне кажется, что мой материал в разрезе 3 статей на каждую тему будет лаконичней и полней, но тут уже решать только вам, дорогие читатели

Не будем повторяться

Да, начнем со смежных статей, не хочу тратить ваше время, если вам не нравится подача и / или посыл

Отправляемся к коду

Базовый принцип, на котором основана работа декораторов — замыкание
Простыми словами, мы просто «Запоминаем» значение переменных в каком-то контексте (пространстве имен)

# Минимальный пример замыкания def summ(a):     # Объявляем функцию внутри другой и используем значение     # переменной из пространства имен объемлющей функции     return lambda b: a + b   # "Запоминаем" 10. При дальнейших вызовах a_plus_b результатом будет 10 + переданное значение a_plus_b = summ(10) # Прибавляем 5 a_plus_b(5) # Out[8]: 15 

Переходим к декораторам

Базово декоратор — это объемлющая функция и оберточная. Объемлющая функция декорирует целевую функцию оберточной функцией, а оберточная функция определяет поведение вызова итогового объекта. Сложно и не интересно, согласен, поэтому пример

# Минимальный пример декоратора def regular_decorator(function):     print("regular_decorator wrap")     # Объявляем функцию внутри функции декоратора, которая принимает любые позиционные и именованные аргументы     # это необходимо для того, чтобы обернуть декорируемую функцию и иметь возможность прокинуть произвольный набор     # аргументов в декорируемую функцию     @wraps(function) # Данная строка не обязательна     def wrapper(*args, **kwargs):         # Выполняем какой-то код до вызова декорируемой функции         print("before regular_decorated call")         # Получаем результат выполнения декорируемой функции         result = function(*args, **kwargs)         # Выполняем какой-то код после вызова декорируемой функции         print("before after_decorated call")         # Возвращаем результат выполнения функции         return result      # Возвращаем оберточную функцию     return wrapper   # Декорируем целевую функцию @regular_decorator def regular_decorated():     print("regular_decorated call") # Здесь консоль выведет regular_decorator wrap  # Вызываем декорируемую функцию regular_decorated() # Здесь в консоль выводится следующее # before regular_decorated call # regular_decorated call # before after_decorated call 

Декораторы бывают сложнее

Периодически (а иногда и часто), у вас появляется желание / необходимость делать ваши декораторы гибкими или настраиваемыми. Знакомьтесь с параметризируемыми декораторами.

# Минимальный пример декоратора, принимающего параметры def parametrized_decorator(     target: Callable | None = None,     /, # Указываем, что параметр target может передаться исключительно как позиционный     multiply_result: float = 2.0,     add_to_result: float = 0.0, ):     print("parametrized_decorator parametrize")     # Объявляем функцию декоратор     def decorator(function):         print("regular_decorator wrap")         # Объявляем оберточную функцию         @wraps(function)  # Данная строка не обязательна         def wrapper(*args, **kwargs):             print("before parametrized_decorator call")             result = function(*args, **kwargs)             print("before parametrized_decorator call")             return (result + add_to_result) * multiply_result # Умножаем на значение, переданное как параметр          # Возвращаем оберточную функцию         return wrapper      # Если декоратор используется без параметров и первым аргументом является вызываемый объект,     # то сразу же оборачиваем target функцией декоратором     if isinstance(target, Callable):         return decorator(target)     # Если первый аргумент не передан, возвращаем функцию декоратор     return decorator   # Декорируем целевую функцию без параметров ( в данном случае isinstance(target, Callable) is True) @parametrized_decorator def default_parametrized_decorated(value):     print("default_parametrized_decorated call")     return value # Здесь консоль выведет # parametrized_decorator parametrize # regular_decorator wrap  # Вместо использования @parametrized_decorator вы также можете сделать следующее parametrized_decorator(default_parametrized_decorated) parametrized_decorator()(default_parametrized_decorated)  # Вызываем декорируемую функцию default_parametrized_decorated(1) # Консоль выведет следующее # before parametrized_decorator call # default_parametrized_decorated call # before parametrized_decorator call # Out[19]: 2.0  # Декорируем целевую функцию c параметром ( в данном случае isinstance(target, Callable) is False) @parametrized_decorator(multiply_result=4) def parametrized_decorated_with_multiple_result(value):     print("parametrized_decorated_with_multiple_result call")     return value # Здесь консоль выведет # parametrized_decorator parametrize # regular_decorator wrap  # Вызываем декорируемую функцию parametrized_decorated_with_multiple_result(1) # Консоль выведет следующее # before parametrized_decorator call # parametrized_decorated_with_multiple_result call # before parametrized_decorator call # Out[21]: 4 

Декоратор — это не одинокая структура

Да, на функцию можно вешать любое количество декораторов (порядок важен)

# Также вы можете использовать декораторы вместе, например @parametrized_decorator(add_to_result=4) @parametrized_decorator(multiply_result=2) def double_parametrized_decorated_with_multiple_result(value):     print("double_parametrized_decorated_with_multiple_result call")     return value # Здесь консоль выведет # parametrized_decorator parametrize # parametrized_decorator parametrize # regular_decorator wrap # regular_decorator wrap  # Вызываем декорируемую функцию double_parametrized_decorated_with_multiple_result(1) # before parametrized_decorator call # before parametrized_decorator call # double_parametrized_decorated_with_multiple_result call # before parametrized_decorator call # before parametrized_decorator call # Out[21]: 12.0  # Да, порядок имеет значение @parametrized_decorator(multiply_result=2) @parametrized_decorator(add_to_result=4) def double_parametrized_decorated_with_multiple_result(value):     print("double_parametrized_decorated_with_multiple_result call")     return value  # Вызываем декорируемую функцию double_parametrized_decorated_with_multiple_result(1) # before parametrized_decorator call # before parametrized_decorator call # double_parametrized_decorated_with_multiple_result call # before parametrized_decorator call # before parametrized_decorator call # Out[24]: 20.0 

Заключение

В данной статье мы узнали о существовании паттерна «замыкание», узнали о существовании декораторов и увидели несколько простых примеров

В следующих статьях я планирую рассказать о том более сложных примерах, таких как:

  • Универсальное декорирование обычных функций, методов, методов классов, статических методов и опционально асинхронных функций

  • Классах, выступающих в роли декоратора

  • Необычные фичи из разряда декорирования функции чем угодно, что можно вызвать

  • И многое другое


ссылка на оригинал статьи https://habr.com/ru/articles/882340/