[英]Change value of variable in Python decorator
from functools import wraps
class EventCounter(object):
def __init__(self, schedules=None, matters=None):
self.counter = 0
self.schedules = schedules
self.matters = matters
if not isinstance(schedules, list) or not isinstance(matters, list):
raise ValueError("schedules and matter must be list.")
if not all([schedules, matters]):
raise ValueError("Need to set schedules and matters both.")
def __call__(self, f):
@wraps(f)
def wrapper(*args, **kwargs):
if self.schedules:
if self.counter == self.schedules[0]:
self.schedules.pop(0)
if self.matters:
self.matters.pop(0)
wrapper.counter = self.counter
wrapper.schedule = self.schedules[0] if self.schedules else None
wrapper.matter = self.matters[0] if self.matters else None
self.counter += 1
return f(*args, **kwargs)
return wrapper
if __name__ == '__main__':
@EventCounter([2, 4, 8, 16], [0, 1, 2, 3, 4])
def reset():
print(f'{reset.counter}: {reset.matter}')
for _ in range(20):
reset()
Is it possible to change the value of variable in decortor?是否可以更改装饰器中变量的值?
In this case, I want to reset counter
to 0
, like在这种情况下,我想将
counter
重置为0
,例如
def reset():
print(f'{reset.counter}: {reset.matter}')
if reset.counter == 12:
reset.counter = 0
But the code above doesn't work for me.但是上面的代码对我不起作用。
Any suggestion?有什么建议吗?
Also, I want to change members of Decorator like schedules
and matters
另外,我想更改 Decorator 的成员,例如
schedules
和matters
Thanks for @ShadowRanger 's advice, inspire me a lot.感谢@ShadowRanger 的建议,给了我很多启发。
There is a conciser way to deal with this, shown as below:有一种更简洁的方法来处理这个问题,如下所示:
from collections import deque
from functools import wraps
def EventCounter(schedules, matters):
if not (schedules and matters):
raise ValueError("schedules and matters must be non-empty.")
def wrap_func(func):
@wraps(func)
def wrapper(*args, **kwargs):
wrapper.schedules = deque(wrapper.schedules)
wrapper.matters = deque(wrapper.matters)
if wrapper.schedules and wrapper.counter == wrapper.schedules[0]:
wrapper.schedules.popleft()
if wrapper.matters and len(wrapper.matters) != 1: # keep the last matter
wrapper.matters.popleft()
wrapper.schedule = wrapper.schedules[0] if wrapper.schedules else None
wrapper.matter = wrapper.matters[0] if wrapper.matters else None
wrapper.counter += 1
return func(*args, **kwargs)
wrapper.counter = 0 # Initialize wrapper.counter to zero before returning it
wrapper.schedules = deque(schedules)
wrapper.matters = deque(matters)
return wrapper
return wrap_func
if __name__ == '__main__':
@EventCounter([2, 4, 8, 16], [0, 1, 2, 3])
def reset():
print(f'{reset.counter}: {reset.matter}')
if reset.counter == 12:
reset.counter = 0
reset.schedules = [1,5,7,11]
reset.matters = [10, 20, 30, 40, 50]
for _ in range(30):
reset()
Your problem is that int
s are immutable, and you're maintaining wrapper.counter
and self.counter
separately, resetting wrapper.counter
to self.counter
on each call (undoing your attempt to reset it via the wrapper).您的问题是
int
是不可变的,并且您分别维护wrapper.counter
和self.counter
,在每次调用时将wrapper.counter
重置为self.counter
(撤消通过包装器重置它的尝试)。 In this case, there is no real benefit to maintaining self.counter
as an instance variable (the EventCounter
object is discarded after decoration completes; it technically exists thanks to closing on self
in wrapper
, but accessing it would be nuts; frankly, the whole class is unnecessary and all of this could be done with simple closures), so the simplest solution is to store a single copy of the counter solely on the wrapper function:在这种情况下,将
self.counter
维护为实例变量并没有真正的好处(装饰完成后EventCounter
对象被丢弃;由于在wrapper
中关闭self
,它在技术上存在,但访问它会很疯狂;坦率地说,整个class 是不必要的,所有这些都可以通过简单的闭包来完成),因此最简单的解决方案是将计数器的单个副本仅存储在包装函数上:
from functools import wraps
class EventCounter(object):
def __init__(self, schedules=None, matters=None):
# Remove definition of self.counter
self.schedules = schedules
self.matters = matters
if not isinstance(schedules, list) or not isinstance(matters, list):
raise ValueError("schedules and matter must be list.")
if not all([schedules, matters]):
raise ValueError("Need to set schedules and matters both.")
def __call__(self, f):
@wraps(f)
def wrapper(*args, **kwargs):
if self.schedules:
if wrapper.counter == self.schedules[0]: # work solely in terms of wrapper.counter
self.schedules.pop(0)
if self.matters:
self.matters.pop(0)
# No need to copy to/from wrapper.counter here
wrapper.schedule = self.schedules[0] if self.schedules else None
wrapper.matter = self.matters[0] if self.matters else None
wrapper.counter += 1
return f(*args, **kwargs)
wrapper.counter = 0 # Initialize wrapper.counter to zero before returning it
return wrapper
if __name__ == '__main__':
@EventCounter([2, 4, 8, 16], [0, 1, 2, 3, 4])
def reset():
print(f'{reset.counter}: {reset.matter}')
if reset.counter == 12:
reset.counter = 0
for _ in range(20):
reset()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.