Всем привет, меня зовут Аббакумов Валерий.
Я 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/
Добавить комментарий