Всем привет, меня зовут Аббакумов Валерий.
Я 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
Заключение
В данной статье мы узнали о существовании паттерна "замыкание", узнали о существовании декораторов и увидели несколько простых примеров
В следующих статьях я планирую рассказать о том более сложных примерах, таких как:
Универсальное декорирование обычных функций, методов, методов классов, статических методов и опционально асинхронных функций
Классах, выступающих в роли декоратора
Необычные фичи из разряда декорирования функции чем угодно, что можно вызвать
И многое другое
