You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

核心原因分析

  1. Widget实例方法缺少self参数
    Python类的实例方法必须以self作为第一个参数(代表实例本身),省略后会导致参数不匹配的TypeError。若强行运行错误代码,self变量会指向错误对象,最终造成回调绑定混乱,出现重复的callback2。

  2. MyDataClass未初始化回调列表
    MyDataClass中未在初始化阶段创建self.callbacks列表,首次调用addCallbackFunction会直接抛出AttributeError。若在类级别定义callbacks = [],虽不会报错,但结合单例实现的缺陷,可能导致回调被错误覆盖。

  3. 单例__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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 21:12:57