装饰器跟踪号码函数调用 - 不工作

时间:2016-08-22 19:03:33

标签: python debugging decorator

为什么count_calls_bad版本在返回后没有保留添加的函数属性(装饰器将.calls添加到传入的函数中)?我理解第二个(好的)版本是绑定在内部函数内部而不是坏版本,它试图创建一个绑定func属性的闭包,但我认为"坏"版本将保持对闭合变量的引用,允许我得到与" good"相同的结果。版本

   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>),并且该对象没有属性{{1因为调用是绑定在实际未修饰的calls上的。

您实际上可以强制进入原始的未修饰函数并使用以下hackery获取正确更新的属性:

recur_n

我接下来的想法是使用functools @wraps来维护原始的未修饰的函数名称,因为这基本上就是我在上面所做的事情,进入装饰器并拔出未修饰的名称&#39; s {{ 1}}属性。

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

这至少得到了一个结果,但结果为零。所以现在,我已经回答了我自己的原始问题,但我最终得到了一个新问题。为什么,假设@wraps更新了函数以便recur_n现在引用recur_n而不是inner,我是否得到0而不是11?

@wraps似乎复制了该函数的签名,但是不保留引用或复制其他数据,如变量或属性?

2 个答案:

答案 0 :(得分:1)

您从未定义recur_n,至少不会在已发布的代码中定义。您将装饰器应用于recur_10

答案 1 :(得分:0)

正如您所发现的那样,您无法查看计数的原因是因为模块顶层的recur_n名称是指从包含的函数inner返回的装饰者。它没有引用原始的recur_n函数(尽管您可以通过包装函数的__closure__属性访问该函数。)

使用functools.wraps不会改变该基本问题。它所做的就是将原始函数的一些属性复制到包装函数中。因此,您在顶层看到的recur_n功能会将__name__设置为"recur_n"而不是"inner",并且__doc__recur_n相匹配原始wraps的文档字符串(如果有的话)。 __dict__调用还会将函数__name__中任何属性的当前值复制到新的包装函数中。

设置函数wraps不会更改模块命名空间中名称所指的内容。实际上,在使用recur_n之后,两个函数(原始__name__和包装函数)将具有相同的"recur_n"属性recur_n。该模块只能有一个名称0引用的东西,它可以是其中之一,也可以是完全不同的东西!

至于当您在装饰器中使用recur_n.calls时检查wraps时看到__dict__的原因,那是因为零作为{{}}的一部分被复制了{1}}原始函数。但是你不能看到增加的计数,因为副本只发生一次(并且Python整数是不可变的,所以它们不能就地更新)。