Написание декораторов с дополнительной обёрткой и без неё

Есть два декоратора, первый без дополнительной функции, в качестве параметров принимает сразу и функцию, которой мы добавляем дополнительный функционал, и аргументы этой же функции:

def test(*args): #Наша функция, тест, которая будет принимать произвольное количество параметров.
    print(*args)

def call_start_end(func, *args):
    print('Начало теста')
    func(*args)
    print('Конец теста')

call_start_end(test, 'Тестируем', 'Ещё не закончили', '...')
# Вывод :
# Начало теста
# Тестируем, Ещё не закончили, ...
# Конец теста

Второй декоратор уже использует дополнительную функцию, чтобы обернуть обёртку:

def test(*args):
    print(*args)

def prewrapper(func): #Обёртка для обёртки, которая принимает функцию.
    def call_start_end(*args): # Сама обёртка, которая принимает аргументы для функции, функционал которой мы расширяем.
        print('Начало теста')
        func(*args) 
        print('Конец теста')
    return call_start_end

prewrapper(test)('Тестируем', 'Ещё не закончили', '...')
# Вывод:
# Начало теста
# Тестируем Ещё не закончили ...
# Конец теста

Так вот буду благодарен, если объясните зачем оборачивать обертку еще одной оберткой, в которую мы отдельно передаем функцию, вместо того, чтобы как в первом примере декоратора, не передавать их в одну обёртку? Мне кажется отчасти декораторы сложны для понимания именно из-за этого момента, сразу так и не ясно зачем дополнительно оборачивать в ещё одну обёртку. Заранее спасибо за ответ.


Ответы (1 шт):

Автор решения: CrazyElf

Ответ простой - чтобы использовать эти декораторы не "вручную", а именно как декораторы - задекорировав через @ некоторые функции этим декоратором.

@call_start_end
def test(*args): #Наша функция, тест, которая будет принимать произвольное количество параметров.
    print(*args)

test('Тестируем', 'Ещё не закончили', '...')

Ошибка!

TypeError: 'NoneType' object is not callable

@prewrapper
def test(*args):
    print(*args)
    
test('Тестируем', 'Ещё не закончили', '...')

Всё работает как задумано.

Начало теста
Тестируем Ещё не закончили ...
Конец теста

При использовании декораторов через @ вы можете передать в декоратор и аргументы и функцию, но только по отдельности: декоратору - функцию, самой функции - аргументы. Нет единого вызова, чтобы передать сразу всё. А если вы захотите, чтобы ещё и у самого декоратора были какие-то аргументы, которые ему самому можно передать, то вам придётся писать уже целых два враппера:

  • внешний враппер будет принимать параметры декоратора
  • внутренний будет принимать ссылку на функцию
  • функция внутри этих двух врапперов уже будет выполнять собственно нужный вам функционал

Поэтому всё выглядит внешне так сложно, но если разобраться, то всё довольно логично и понятно зачем и почему так всё сделано.

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

test('Тестируем', 'Ещё не закончили', '...')

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

→ Ссылка