Декораторы для самых маленьких (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/


Интересные статьи

Интересные статьи

И рассказываем, чтобы сэкономить Ваше времяЧтобы перелезть через этот высоченный забор понимания, как же все-таки работает сейчас КЭДО в Битрикс24 мы...• Просмотрели 384 минуты видеоуроков и рол...
Как можно получать элементы инфоблоков в 1С-Битрикс в ядре d7?У \CIBlockElement::getList(), на первый взгляд, есть свои преимущества, она задокументирована, о ней много статей на форумах. Её дебагинг ...
Технологии связи 5G сулят мгновенную загрузку видеоконтента на мобильные устройства, онлайн-игры с минимальными задержками, видеосвязь без помех и квадратиков и много других восхитите...
Недавно на проекте интегрировал модуль CRM Битрикса c виртуальной АТС Ростелеком. Делал по стандартной инструкции, где пошагово показано, какие поля заполнять. Оказалось, следование ей не гаран...
В далеком 2009-м на Хабре уже возникал холиварный шуточный пост с вопросом "Умирает ли Delphi?", а неким энтузиастом (Jamie Ingilby) в поддержку были созданы 2 сайта, которые утвердительно отвеча...