Почему рекурсивная функция в декораторе вызывает свою внутреннюю функцию

#python #python-3.x #recursion #closures #decorator

#python #python-3.x #рекурсия #замыкания #декоратор

Вопрос:

почему рекурсивный func(n) в строке 4 вызывает inner функцию n раз, а не только исходную функцию fact .

 def log(func):
    def inner(n):
        print('inner()')
        result = func(n)
        return result
    return inner

def fact(n):
    return 1 if n < 2 else n*fact(n-1)

fact = log(fact)

if __name__ == '__main__':
    fact(4)
 

Вывод, что меня смутило (ожидаемая печать выполняется только один раз):

 inner()
inner()
inner()
inner()
 

В строке fact = log(fact) декоратор log возвращает ссылку на inner объект функции. Эта ссылка присваивается name fact и fact.__name__ теперь печатается inner . Кроме того, когда я проверяю id(fact) , она возвращает новое значение.

Внутри оформленной fact функции свободная переменная func по-прежнему указывает на исходный fact объект функции. Я могу убедиться в этом, используя id(func) or func.__name__ внутри inner , это то же самое, что и до decorate . И часть, которую я просто не понимаю. Когда рекурсивная функция выполняется в строке result = func(n) внутри inner , func(n) вызывает оформленный fact (или inner ) не оригинальный fact . Это меня смущает, потому func что все еще указывает на оригинал fact .

Почему рекурсивная функция внутри декоратора вызывает свою внутреннюю функцию, а не исходную функцию, которая была передана декоратору и все еще имеет ссылку на нее?

Комментарии:

1. inner заменяет fact . Любой рекурсивный вызов fact является вызовом inner .

2. На самом деле она вызывает оба, но fact вызывается внутри inner вызова.

Ответ №1:

Как уже объяснил @ inner() khelwood, печатается 4 раза, потому что рекурсия вызывает inner функцию 4 раза, легко.

Ваша fact функция вызывается в этой строке result = func(n) внутри inner функции. Если у вас print(fact.__name__) есть вывод inner , потому что fact это inner функция.

Это может сбить с толку, потому что каждая функция, которую вы передаете в свой log декоратор, станет inner функцией. Чтобы избежать этой путаницы, я предлагаю использовать wraps декоратор следующим образом:

 from functools import wraps
def log(func):

    @wraps(func)
    def inner(n):
        print('inner()')
        result = func(n)
        return result
    return inner
 

Таким образом, func вы передаете внутри своего декоратора сохранит исходную идентичность и print(fact.__name__) будет печатать fact .

Комментарии:

1. Если я это делаю func.__name__ , или id(func) внутри inner , он печатает fact и по-прежнему имеет ссылку на исходную функцию. Почему тогда func(n) вызывает inner ?

2. Потому fact что это и имя вашей функции, и переменная. Вы не должны этого делать, потому что это сбивает с толку. Когда вы пишете fact , это ссылается на переменную, а не на функцию, и поскольку значение переменной fact является log(fact) log функцией, вызывается и возвращается inner . Поэтому измените имя вашей переменной на что-то вроде logging_fact , и это будет понятнее.

3. @edzoons В основном то, что вы log() делаете, возвращает inner функцию, любая переменная, которой вы присваиваете log() , будет внутренней функцией. Вы перезаписываете fact имя, а этого делать не следует.

4. Ох. Наконец-то я понял, что вы оба (@JLeno46 и @khelwood ) пытались мне сказать. Исходная fact функция загружает глобальное имя fact , которое, кстати, является оформленным fact . Спасибо.

5. @khelwood если я изменю имя fact на logging_fact , функция больше не будет оформлена. Тем не менее, это помогло мне понять это.