Всем привет, меня зовут Аббакумов Валерий.
Я 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("after regular_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
# after regular_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("after 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
# after 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
# after 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
# after parametrized_decorator call
# after 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
# after parametrized_decorator call
# after parametrized_decorator call
# Out[24]: 20.0
Заключение
В данной статье мы узнали о существовании паттерна "замыкание", узнали о существовании декораторов и увидели несколько простых примеров
В следующих статьях я планирую рассказать о более сложных примерах, таких как:
Универсальное декорирование обычных функций, методов, методов классов, статических методов и опционально асинхронных функций
Классах, выступающих в роли декоратора
Регистрации объектов
Работе с сигнатурами декорируемых функций
Необычные фичи из разряда декорирования функции чем угодно, что можно вызвать
И многое другое