为什么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似乎复制了该函数的签名,但是不保留引用或复制其他数据,如变量或属性?
答案 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整数是不可变的,所以它们不能就地更新)。