Порядок выполнения декораторов в Python

  • Опубликовано:
  • Теги: python
На просторах Интернета натолкнулся на забавный пример использования нескольких декораторов:

def mult(f):
    return lambda x: func(x * x)

def add(func):
    return lambda x: func(x + 4)

@mult
@add
def f1(x):
    return x

@add
@mult
def f2(x):
    return x

>>> f1(2)
8
>>> f2(2)
36
Из примера очевидно, что декораторы исполняются в порядке следования. В официальной документации об этом сказано что:

@f1(arg)
@f2
def func(): pass
эквивалентно

def func(): pass
func = f1(arg)(f2(func))
Гугл привел к потрясающе подробному и исчерпывающему раскрытию темы декораторов на stackoverflow. К тому же вопросу дан и более краткий ответ, который привожу здесь:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() ## returns "<b><i>hello world</i></b>"
Меня этот пример сбил с толку, т.к., если спроецировать на пример с числами, то можно сделать вывод, как будто при вызове f2(2) должны выполняться действия ((2 * 2) + 4), что в результате дало бы 8, если выполнять в арифметическом порядке вложенности. Но интерпретатор выдает нам 36. Стало любопытно, как выглядит байт-код этой функции

>>> dis.dis(f2)
  6           0 LOAD_DEREF               0 (func)
              3 LOAD_FAST                0 (x)
              6 LOAD_CONST               1 (4)
              9 BINARY_ADD
             10 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             13 RETURN_VALUE
Здесь видно, что сначала к аргументу прибавляется 4, а затем – с измененным аргументом – вызывается функция (которая декорируется еще раз). Следовательно, в примере с html-тегами сначала рисуется внешний тег <b>, а затем внутренний <i>.

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

Вот еще пара декораторов без всякой вложенности для большей наглядности:

def first(func):
    def wrapper():
        print("First")
        func()
    return wrapper

def second(func):
    def wrapper():
        print('Second')
        func()
    return wrapper

@first
@second
def myfunc():
    print("Original function")
И вызовем функцию в консоли Python:

>>> myfunc()
First
Second
Original function
Мне стало понятней, а вам?