修改Python装饰器中变量的值


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()

是否可以改变decortor中变量的值?

在本例中,我想将counter重置为0,如

def reset():
print(f'{reset.counter}: {reset.matter}')
if reset.counter == 12:
reset.counter = 0

但是上面的代码不适合我。

任何建议吗?

另外,我想更改Decorator的成员,如schedulesmatters

解决方案:

谢谢@ shadowwranger的建议,给了我很多启发。

有一个更简洁的方法来处理这个问题,如下所示:

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()

您的问题是ints是不可变的,并且您分别维护wrapper.counterself.counter,在每次调用时将wrapper.counter重置为self.counter(取消通过包装器重置它的尝试)。在这种情况下,将self.counter维持为实例变量并没有真正的好处(EventCounter对象在装饰完成后被丢弃;由于wrapper关闭self,它在技术上是存在的,但访问它将是坚果;坦率地说,整个类是不必要的(所有这些都可以通过简单的闭包完成),因此最简单的解决方案是将计数器的单个副本单独存储在包装器函数中:

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()

上网试试!

最新更新