当嵌套两个存储和更改状态信息的Python装饰器时,将内部装饰函数的状态传递给外部装饰器的最佳方法是什么?
例如,我们可能将装饰器定义为
SELECT * FROM city WHERE population > 200;
然后我们可以如下装饰一个函数
def time_this(func):
@functools.wraps(func)
def wrapper(*args, **kwargs) :
start = time.process_time()
rtn_val = func(*args, **kwargs)
wrapper.time_taken = time.process_time() - start
return rtn_val
return wrapper
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs) :
wrapper.num_calls += 1
return func(*args, **kwargs)
wrapper.num_calls = 0
return wrapper
但是,如果我们尝试
@time_this
@count_calls
def my_func():
time.sleep(0.5)
print("Hello World!")
我们得到的输出是
my_func()
print(my_func.time_taken)
print(my_func.num_calls)
(请注意,Hello World!
0.5007079059998887
0
属性始终为0。)
为澄清起见,我完全理解为什么发生这种情况,但是我想找出解决此问题的最佳方法是什么(以便上面的代码可以实现您希望的效果,并且更新包装器中的num_calls
。
答案 0 :(得分:1)
您可以将一个带有结果的字典设置为包装器,该字典将在装饰器之间共享:
import functools
import time
def time_this(func):
@functools.wraps(func)
def wrapper(*args, **kwargs) :
time1 = time.time()
rtn_val = func(*args, **kwargs)
time2 = time.time()
wrapper.results['time_this'] += (time2 - time1) * 1000.0
return rtn_val
results = getattr(wrapper, 'results', {})
results['time_this'] = 0
setattr(wrapper, 'results', results)
return wrapper
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs) :
rtn_val = func(*args, **kwargs)
wrapper.results['count_calls'] = wrapper.results['count_calls'] + 1
return rtn_val
results = getattr(wrapper, 'results', {})
results['count_calls'] = 0
setattr(wrapper, 'results', results)
return wrapper
@time_this
@count_calls
def my_func():
print("Hello World!")
for i in range(10):
my_func()
print(my_func.results)
print('avg:', my_func.results['time_this'] / my_func.results['count_calls'])
打印:
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
{'count_calls': 10, 'time_this': 0.03719329833984375}
avg: 0.003719329833984375
答案 1 :(得分:0)
您知道,num_calls
在外部包装器中为静态0的原因是,functools.wraps
更新了包装器__dict__
,将包装器冻结为原始值。有两种处理方法。
如果知道装饰器的顺序,则可以使用它在包装器中创建的__wrapped__
属性来访问实际属性:
print(my_func.__wrapped__.num_calls)
另一个选择是查看wraps
的其他参数。默认情况下,__dict__
被更新。但是为什么不将其重新分配给新对象:
def mywraps(wrapped):
return functools.wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS + ('__dict__',),
updated=())
def time_this(func):
@mywraps(func)
def wrapper(*args, **kwargs) :
start = time.process_time()
rtn_val = func(*args, **kwargs)
wrapper.time_taken = time.process_time() - start
return rtn_val
return wrapper
def count_calls(func):
@mywraps(func)
def wrapper(*args, **kwargs) :
wrapper.num_calls += 1
return func(*args, **kwargs)
wrapper.num_calls = 0
return wrapper
现在,链中的所有功能对象都共享相同的手动分配的属性,因为
>>> my_func.__dict__ is my_func.__wrapped__.__dict__ is my_func.__wrapped__.__wrapped__.__dict__
True
您严格不需要mywraps
装饰器。我只是为了避免每次都为wraps
设置参数而提供的便利,就像wraps
本身为update_wrapper
带来便利一样。