我在一个函数上有两个装饰器。 每个装饰器都会向函数添加一个属性,但第一个装饰器的属性在启动后不会传递给该函数。
为什么f3.calls
在以下代码中保持为0?
def memorize(fn):
result_list3 = {}
@wraps(fn)
def wrapper(n):
wrapper.calls2 += 1
print("memorize calls2", wrapper.calls2)
found = result_list3.get(n)
if found is not None:
result = found
else:
result = fn(n)
result_list3.update({n: result})
# print(result_list3)
return result
wrapper.calls2 = 0
return wrapper
def countt(fn):
@wraps(fn)
def wrapper(n):
wrapper.calls += 1
print("countt calls", wrapper.calls)
result = fn(n)
return result
wrapper.calls = 0
return wrapper
@memorize
@countt
def f3(n):
if n < 3:
return n
else:
result = f3(n - 1) + 2 * f3(n - 2) + 3 * f3(n - 3)
return result
if __name__ == '__main__':
print(f3(10))
print(f3.calls) # 0
print(f3.calls2) # 25
这是日志:
memorize calls2 1
countt calls 1
memorize calls2 2
countt calls 2
memorize calls2 3
countt calls 3
memorize calls2 4
countt calls 4
memorize calls2 5
countt calls 5
memorize calls2 6
countt calls 6
memorize calls2 7
countt calls 7
memorize calls2 8
countt calls 8
memorize calls2 9
countt calls 9
memorize calls2 10
countt calls 10
memorize calls2 11
countt calls 11
memorize calls2 12
memorize calls2 13
memorize calls2 14
memorize calls2 15
memorize calls2 16
memorize calls2 17
memorize calls2 18
memorize calls2 19
memorize calls2 20
memorize calls2 21
memorize calls2 22
memorize calls2 23
memorize calls2 24
memorize calls2 25
1892
0
25
答案 0 :(得分:1)
每个装饰器为名称wrapper
分配一个新函数f3
,这意味着有三个不同的函数被调用:原始f3
,wrapper
从{返回} {1}}以及countt
返回的wrapper
。在最后一行中,memorize
引用print(f3...)
中的wrapper
。但在memorize
中的wrapper
内,countt
指的是来自wrapper.calls += 1
的{{1}}而不是wrapper
中的countt
。因此,您没有看到memorize
的效果,因为您正在查看错误的函数对象。但是,如果您选中wrapper.calls += 1
,您将看到正确的值(11)。
最终f3.__wrapped__.calls
(来自f3
的{{1}})甚至具有wrapper
属性的原因是因为memorize
calls
将@wraps
中的memorize
属性从wrapper
复制到countt
。
答案 1 :(得分:0)
您正在查看错误的calls
属性。以下是发生的事情:
countt
生成wrapper()
函数来替换修饰函数。我们称之为countt.<local>.wrapper()
(即使functools.wraps()
将__name__
属性设置为f3
)。这包装了原始函数fn
,functools.wraps()
将所有函数属性复制到countt.<local>.wrapper()
。您向calls
添加了countt.<local>.wrapper()
属性,因此我们称之为countt.<local>.wrapper.calls
。
memorize
生成wrapper()
函数来替换修饰函数fn
。我们称之为memorize.<local>.wrapper()
。这包含传入的函数fn
,这里是countt.<local>.wrapper()
。 functools.wraps()
将所有功能属性复制到memorize.<local>.wrapper()
,包括countt.<local>.wrapper.calls
,成为memorize.<local>.wrapper.calls
,设置为0
。您向calls2
添加了memorize.<local>.wrapper()
属性,因此我们称之为memorize.<local>.wrapper.calls2
。
然后运行代码。 countt.<local>.wrapper()
不断更新countt.<local>.wrapper.calls
。请注意,memorize.<local>.wrapper.calls
从未在此处更改,这是不同的独立属性。
然后打印f3.calls
和f3.calls2
。 f3.calls
为0,因为它真的是memorize.<local>.wrapper.calls
,而不是countt.<local>.wrapper.calls
。
如果您运行的是Python 3.2或更高版本,则可以使用__wrapped__
属性访问包装函数; f3.__wrapped__
此处为countt.<local>.wrapper()
,因此您可以看到正确的 calls
属性:
print(f3.__wrapped__.calls)