Контроль потока в декораторе - PullRequest
0 голосов
/ 09 сентября 2018

У меня проблемы с пониманием потока управления в программе Python, имеющей декоратор.

def executor(func):
    def innerExecutor():
        #print("inside executor")
        print("------------------------")
        func()
        print("------------------------")
    return innerExecutor

@executor
def multiply():
    n1 = 10
    n2 = 20
    print("multiplication = ", (n1 * n2))

multiply()

С какими проблемами я сталкиваюсь: когда вызывается функция executor() и она возвращает ссылку innerExecutor(), где хранится эта ссылка? Когда управление фактически передается внутренней функции? Я очень новичок в Python.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

возвращает ссылку на innerExecutor (), где эта ссылка хранится?

Когда управление фактически передается внутренней функции?

Он нигде не хранится, он называется так:

innerExecutor(multiply)

Вот почему ваш метод декоратора должен возвращать свою собственную ссылку, в противном случае это будет эквивалентно следующему:

None(multiply)  # TypeError: 'NoneType' object is not callable

Вот документация поведения выше

0 голосов
/ 09 сентября 2018

Синтаксис

@some_decorator
def somefunc():
   ...

эквивалентно:

somefunc = some_decorator(somefunc)

Это возможно в Python, потому что функции первоклассные объекты . Их можно передавать в качестве аргументов и возвращать из других функций - так называемые функции высшего порядка .

В вашем примере последний вызов multiply() на самом деле запускает innerExector(), функцию, созданную в декорированном определении multiply(). Чтобы понять, что здесь происходит, присмотритесь к определению executor:

def executor(func):
    def innerExecutor():
        #print("inside executor")
        print("------------------------")
        func()
        print("------------------------")
    return innerExecutor

При коде:

@executor
def multiply():
    ...

выполняется, функция executor вызывается с функцией multiply в качестве аргумента. executor определяет функцию innerExecutor() и возвращает ее , но не выполняет ее . Этот экземпляр innerExecutor() вызывается вместо всех будущих ссылок на multiply().

Но подождите - обычно мы не хотим полностью заменять декорируемую функцию. innerExecutor() необходимо вызвать декорированную функцию в какой-то момент. Напомним, что декорированная функция определения:

@executor
def multiply():
    ...

эквивалентно:

def multiply():
    ...
multiply = executor(multiply)

Передача функции, которая будет оформлена как аргумент декоратору, позволяет определению innerExecutor() вызывать ее. Но подождите - innerExecutor() не вызывается намного позже, после того, как executor() вернулся и его аргумент func вышел из области видимости. Так почему же это работает?

Благодаря еще одной функции Python, называемой замыканием . Это означает, что внутренняя функция может ссылаться на локальные переменные, определенные во вложенной области действия, включая аргументы, передаваемые во внешнюю функцию executor - даже после выхода из охватывающей области действия . Волшебная особенность замыканий заключается в том, что интерпретатор обнаруживает зависимость определения внутренней функции от локальной переменной внешней функции и сохраняет ее доступной даже после возврата executor(). Эта ссылка содержит более подробную информацию о замыканиях.

Наконец, вызов multiply() в последней строке фактически вызывает экземпляр innerExecutor(), который был создан при выполнении декорированного определения, что, в свою очередь, вызывает исходную неокрашенную версию multiply().

Каждый раз, когда декоратор используется для декорирования определения функции, он создает новый экземпляр внутренней функции, который заменяет недекорированную функцию. Таким образом, если вы украсите 5 разных функций, будет создано 5 разных экземпляров innerExecutor(). Позже, когда вызывается любая из этих декорированных функций, она вместо этого вызывает вместо этого соответствующий экземпляр innerExecutor(), который, в свою очередь, вызывает соответствующую недекорированную функцию.

...