Сообщить об ошибке.

Вложенные декораторы в Python

Вложение декораторов один в другой

В Python можно применить несколько декораторов к функции, укладывая их друг на друга. При использовании этой возможности каждый декоратор должен указываться в отдельной строке:

@decorator1
@decorator2
@decorator3
def func():
    ...

Несколько декораторов, следующих подряд, не декорируют друг друга. Они изменяют функцию и ее измененный вариант в качестве аргумента передают друг другу в порядке следования.

Здесь декорируемая функция func() передается трем различным декораторам decorator1, decorator2 и decorator3. Каждый декоратор обрабатывает результат, возвращаемый предыдущим декоратором. Последний декоратор в списке, в данном случае decorator3, будет задействован первым.

Для более детального понимания, что происходит в примере выше, запишем его следующим образом:

func = decorator1(decorator2(decorator3(func)))

Другими словами, декораторы начинают обрабатывать функцию, к которой они применяются, изнутри. Пример со "stackoverflow":

def bold(func):
    def wrapper():
        val = func()
        # Вставка HTML кода до и после 'val'
        return '<b>' + val + '</b>'
    return wrapper

def italic(func):
    def wrapper():
        val = func()
        # Вставка HTML кода до и после 'val'
        return '<i>' + val + '</i>'
    return wrapper

@bold
@italic
def say():
    return "hello"

print(say())
# <b><i>hello</i></b>

Еще пример. Возьмем декораторы repeater() и delayed() из материала "Декораторы с аргументами" и применим их к функции, которая имитирует проверку сайта на доступность:

@repeater(repeat=3)
@delayed(delay=30)
def monitor(uri):
    print(f'Проверка {uri} на доступность')
    
monitor('https://docs-python.ru/')
# 1: Спим 30 сек.
# Проверка https://docs-python.ru/ на доступность
# 2: Спим 30 сек.
# Проверка https://docs-python.ru/ на доступность
# 3: Спим 30 сек.
# Проверка https://docs-python.ru/ на доступность

Обратите внимание в какой последовательности выполняются декораторы. Сначала выполняется декоратор @delayed - происходит задержка выполнения на 30 секунд, а затем выполняется декоратор @repeater, который будет повторять предыдущую операцию 3 раза.

Попробуем изменить очередность декораторов в коде и логика выполнения поменяется:

@delayed(delay=30)
@repeater(repeat=3)
def monitor(uri):
    print(f'Проверка {uri} на доступность')
    
monitor('https://docs-python.ru/')
# Спим 30 сек.
# 1: Проверка https://docs-python.ru/ на доступность
# 2: Проверка https://docs-python.ru/ на доступность
# 3: Проверка https://docs-python.ru/ на доступность

В случае удачной реализации декораторов и преимущество их вложения друг в друга, дает возможность составлять из них самые разнообразные комбинации.