简体   繁体   English

在 Python 装饰器中更改变量的值

[英]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 的成员,例如schedulesmatters

Solution:解决方案:

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

Try it online! 在线尝试!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM