Home > Blockchain >  Change value of variable in Python decorator
Change value of variable in Python decorator

Time:06-07

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

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

CodePudding user response:

Your problem is that ints 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). 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:

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!

  • Related