Python装饰器内部变量及成员属性修改实现方案
问题描述
初始实现的类式EventCounter装饰器代码如下:
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()
待解决的问题如下:
- 是否可以修改Python装饰器内部的变量值?
- 现有场景下需要在被装饰的
reset函数中将counter值重置为0,尝试实现的代码如下,但逻辑无法正常生效;同时还需要支持修改装饰器的schedules、matters等成员属性,该如何实现?
def reset(): print(f'{reset.counter}: {reset.matter}') if reset.counter == 12: reset.counter = 0
解决方案
感谢@ShadowRanger的建议,更简洁的实现方式如下:
将原类装饰器改写为函数式装饰器,把counter、schedules、matters属性直接绑定到wrapper函数对象上,即可支持在被装饰函数内直接修改属性,实现counter重置、属性动态更新的需求。
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: # 保留最后一个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 # 返回前初始化wrapper的counter属性为0 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()
内容的提问来源于stack exchange,提问作者NEET
相关产品推荐
相关产品推荐

