Всем привет, меня зовут Аббакумов Валерий.
Я 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
Заключение
В данной статье мы узнали о существовании паттерна "замыкание", узнали о существовании декораторов и увидели несколько простых примеров
В следующих статьях я планирую рассказать о более сложных примерах, таких как:
Универсальное декорирование обычных функций, методов, методов классов, статических методов и опционально асинхронных функций
Классах, выступающих в роли декоратора
Регистрации объектов
Работе с сигнатурами декорируемых функций
Необычные фичи из разряда декорирования функции чем угодно, что можно вызвать
И многое другое
Комментарии (13)
yupetr
13.02.2025 18:18Не оченьь понятно написано. В статье есть ссылка на "замыкания" - вот там все понятнее описано.
Extralait Автор
13.02.2025 18:18Спасибо за комментарий! В своих статьях я хочу сделать акцент именно на примерах и минимальных подводках к ним, потому что я сам люблю именно такой материал. Понимаю, что не всем читателям этого достаточно, поэтому и дальше буду прикреплять ссылки на статьи с более подробным описанием
miracle-pony
13.02.2025 18:18Хмммм... Предполагаю, что сложность статьи стоит повысить до средней.
Слишком длинные строчки комментариев "уехали" за край.
Я думал, что знаю как работаю декораторы, после этот статьи я засомневался в себе.
Extralait Автор
13.02.2025 18:18Спасибо за комментарий! В будущих статьях обращу большее внимание на "влезание" контентных блоков во фрейм ленты (сам не обратил внимание, рад, что подметили недочет). Концептуально предполагалось написать простую, среднюю и сложную статью друг за другом (следующий материал действительно будет значительно сложнее)
ComputerPers
13.02.2025 18:18А как быть, если моя функция сама по себе с параметрами?
Vindicar
13.02.2025 18:18Так и в примере функция с параметром. Ничего не изменится.
Сколько бы ни было параметров, они будут проброшены в оригинальную функцию вызовом
Extralait Автор
Я планировал в первую очередь осветить следующие темы: декораторы, дескрипторы, контекстные менеджеры, метаклассы
Как считаете, как лучше поступить:
1) Выложить по вводной статье на каждую тему и последовательно поднимать сложность
2) Полностью закрыть каждую тему последовательно