2016-08-22 5 views
1

Почему версия count_calls_bad не сохраняет добавленный атрибут функции (декоратор добавляет .calls к переданному в func) после его возврата? Я понимаю, что вторая (хорошая) версия является обязательной внутри внутренней функции по сравнению с плохой версией, которая пытается создать закрытие, где привязан атрибут func, но я думал, что «плохая» версия будет поддерживать ссылку на закрытую переменную, позволяя мне получить тот же результат, что и «хорошая» версия.Декоратор для отслеживания вызовов функций функций - не работает

def count_calls_bad(func): 
     func.calls = 0 
     def inner(*args,**kwargs): 
      func.calls += 1 #each call to inner increments func.calls (recur_n.calls) 
      return func(*args,**kwargs) 
     return inner 

    def count_calls_good(func): 
    def inner(*args, **kwargs): 
     inner.calls += 1 
     return func(*args, **kwargs) 
    inner.calls = 0 
    return inner 

    @count_calls_bad 
    def recur_n(num): 
     if num == 0: 
      return 0 
     print (num) 
     return recur_n(num-1) 

    recur_n(10) 
    print(recur_n.calls) #recur_n.calls attribute not bound any longer 

UPDATE: фиксированный код, забыл обновить имя функции после тестирования в редакторе. Теперь recur_n вызывается, а не recur_10.


Кроме того, я играл вокруг и думаю, что этот вопрос recur_n становится inner, а потом, что последняя строка print(recur_n.calls) действительно print(function count_calls_bad.<locals>.inner at 0x000000000364E2F0>), и этот объект не имеет атрибута calls, так как вызовы были связан с фактическим неукрашенным recur_n ,

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

print(recur_n.__closure__[0].cell_contents.calls)

Моя следующая мыслью была затем использовать functools @wraps сохранить оригинальную неукрашенный имя функции, так как это в основном то, что я делаю выше, попадая в декоратор и вытаскивая атрибут call неадресного имени.

from functools import wraps 
     def count_calls_bad(func): 
      func.calls = 0 
      @wraps func 
      def inner(*args,**kwargs): 
       func.calls += 1 #each call to inner increments func.calls (recur_n.calls) 
       return func(*args,**kwargs) 
      return inner 

Это, по крайней мере, дает мне результат, но этот результат равен нулю. Итак, теперь я ответил на свой собственный оригинальный вопрос, но в итоге получился новый. Почему, учитывая, что @wraps обновил функцию, так что recur_n теперь ссылается на recur_n, а не на внутреннюю, я получаю 0, а не 11?

Похоже, что @wraps копирует подпись функции, но не поддерживает ссылку или копирует другие данные, такие как переменные или атрибуты?

ответ

0

Как вы обнаружили, причина, по которой вы не можете видеть счет, состоит в том, что имя recur_n на верхнем уровне вашего модуля относится к оберточной функции inner, возвращенной из декоратора. Он не относится к исходной функции recur_n (хотя вы можете получить доступ к этой функции через атрибут __closure__ функции-обертки).

Использование functools.wraps не меняет эту основную проблему. Все, что он делает, это скопировать некоторые атрибуты исходной функции в функцию обертки. Таким образом, функция recur_n, которую вы видите на верхнем уровне, будет иметь свой __name__, установленный в "recur_n", а не "inner", и это __doc__ будет соответствовать оригиналу docstring recur_n (если бы он был). Вызов wraps также копирует текущие значения любых атрибутов в функции __dict__ функции в новую функцию-обертку.

Установка функции __name__ не изменяет то, что имя ссылается в пространстве имен модулей.Действительно, после использования wraps обе функции (оригинал recur_n и функция обертки) будут иметь тот же атрибут __name__, "recur_n". В модуле может быть только одна вещь, на которую ссылается имя recur_n, и это может быть либо одно из них, либо нечто совершенно другое!

А почему вы видите 0, когда вы проверяете recur_n.calls при использовании wraps в декоратор, это потому, что ноль был скопирован как часть __dict__ исходной функции. Вы не можете видеть увеличивающийся счет, потому что копия только однажды (и целые числа на Python неизменяемы, поэтому они не могут быть обновлены на месте).

1

Вы никогда не определяли recur_n, по крайней мере, в опубликованном коде. Вы применили декоратор к recur_10.

Смежные вопросы