Python3:类变量列表中调用其他类回调函数的异常问题
问题重现
实现单例模式的MyDataClass用于管理数据及回调函数,两个Widget组件分别注册各自的回调方法,预期数据变更时两个组件都能收到通知,但实际回调列表中仅重复出现第二个Widget的回调。
MyDataClass单例代码
class MyDataClass: def __new__(cls): if not hasattr(cls, "instance"): cls.instance = super(MyDataClass, cls).__new__(cls) print(cls.instance) return cls.instance def addCallbackFunction(self, f): self.callbacks.append(f) def _callCallbackFunctions(self): for f in self.callbacks: f()
Widget组件代码
class Widget1(): def __init__(): # 缺少self参数 self.data = MyDataClass() self.data.addCallbackFunction(self.callback1) def callback1(): # 缺少self参数 print("x") class Widget2(): def __init__(): # 缺少self参数 self.data = MyDataClass() self.data.addCallbackFunction(self.callback2) def callback2(): # 缺少self参数 print("y")
异常现象
预期self.callbacks包含callback1和callback2,但实际结果为两个callback2。
核心原因分析
Widget实例方法缺少self参数
Python类的实例方法必须以self作为第一个参数(代表实例本身),省略后会导致参数不匹配的TypeError。若强行运行错误代码,self变量会指向错误对象,最终造成回调绑定混乱,出现重复的callback2。MyDataClass未初始化回调列表
MyDataClass中未在初始化阶段创建self.callbacks列表,首次调用addCallbackFunction会直接抛出AttributeError。若在类级别定义callbacks = [],虽不会报错,但结合单例实现的缺陷,可能导致回调被错误覆盖。单例__init__方法未做初始化保护
当前单例仅重写__new__,每次调用MyDataClass()时__init__仍会执行。若__init__中包含self.callbacks = [],会清空已有回调列表,仅保留最后一次添加的回调(此情况与用户现象不完全匹配,但属于单例实现的常见缺陷)。
修复方案
1. 修正Widget类的实例方法
为所有实例方法添加self参数,确保回调正确绑定到实例:
class Widget1(): def __init__(self): self.data = MyDataClass() self.data.addCallbackFunction(self.callback1) def callback1(self): print("x") class Widget2(): def __init__(self): self.data = MyDataClass() self.data.addCallbackFunction(self.callback2) def callback2(self): print("y")
2. 完善MyDataClass的单例与回调初始化
确保回调列表仅初始化一次,避免__init__重复执行初始化逻辑:
class MyDataClass: _instance = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) print(cls._instance) return cls._instance def __init__(self): if not self._initialized: self.callbacks = [] self._initialized = True def addCallbackFunction(self, f): self.callbacks.append(f) def _callCallbackFunctions(self): for f in self.callbacks: f()
验证修复效果
w1 = Widget1() w2 = Widget2() data = MyDataClass() data._callCallbackFunctions() # 输出: # x # y
更优实现方案
1. 装饰器实现单例(简洁风格)
使用装饰器实现更符合Python习惯的单例,避免手动管理实例状态:
def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class MyDataClass: def __init__(self): self.callbacks = [] def add_callback(self, func): if func not in self.callbacks: # 避免重复添加同一回调 self.callbacks.append(func) def remove_callback(self, func): if func in self.callbacks: self.callbacks.remove(func) def notify_all(self): for func in self.callbacks.copy(): # 遍历副本,避免回调中修改列表引发异常 func()
2. 带数据传递的回调优化
若需在回调中传递最新数据,可修改回调方法接收参数:
# MyDataClass新增数据管理与通知逻辑 @singleton class MyDataClass: def __init__(self): self.callbacks = [] self._data = {} def update_data(self, key, value): self._data[key] = value self.notify_all() def notify_all(self): for func in self.callbacks.copy(): func(self._data) # Widget回调方法接收数据参数 class Widget1(): def callback1(self, data): print(f"Widget1更新:{data}")
3. 标准观察者模式实现
对于复杂场景,采用观察者模式分离主题(数据类)与观察者(Widget)的职责,代码扩展性更强:
class Subject: def __init__(self): self._observers = [] def attach(self, observer): if observer not in self._observers: self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self, data=None): for observer in self._observers.copy(): observer.update(data) @singleton class MyDataClass(Subject): def __init__(self): super().__init__() self._data = {} def update_data(self, key, value): self._data[key] = value self.notify(self._data) class Widget1: def update(self, data): print(f"Widget1显示数据:{data}") class Widget2: def update(self, data): print(f"Widget2显示数据:{data}") # 使用示例 data = MyDataClass() w1 = Widget1() w2 = Widget2() data.attach(w1) data.attach(w2) data.update_data("username", "Andrei") # 输出: # Widget1显示数据:{'username': 'Andrei'} # Widget2显示数据:{'username': 'Andrei'}
内容的提问来源于stack exchange,提问作者Andrei M.

