直接用list存订阅者并遍历通知会导致重复注册、异常中断后续通知、迭代中修改列表报错三类问题;应改用weakset去重防内存泄漏,并用list()或copy()快照遍历确保安全。

直接用 list 存订阅者、手动触发通知就能跑通,但容易漏掉重复注册、通知时修改列表、异常中断后续通知这三类问题。
为什么不能直接用 append 和 for 循环?
常见错误是把观察者存进普通 list,然后在 notify 里遍历调用回调——这会导致两个硬伤:
- 同一观察者多次
subscribe会重复通知(比如界面组件被反复挂载) - 某个观察者回调抛异常,后面所有观察者都收不到通知
- 通知过程中有观察者调用
unsubscribe,会触发RuntimeError: list changed size during iteration
用 set 去重 + copy() 隔离遍历
把观察者存在 set 里,天然去重;每次通知前用 list(self._observers) 或 self._observers.copy() 拿快照,避免迭代时修改原集合:
class Subject:
def __init__(self):
self._observers = set()
<pre class="brush:python;toolbar:false;">def subscribe(self, observer):
self._observers.add(observer) # 自动去重
def unsubscribe(self, observer):
self._observers.discard(observer) # 不报错,比 remove 安全
def notify(self, event):
for obs in list(self._observers): # 快照,安全
try:
obs(event)
except Exception:
pass # 单个失败不阻断其他
如何支持带参数的回调和弱引用防内存泄漏?
实际项目中观察者常是对象方法(如 obj.on_update),直接存强引用会阻止 obj 被回收。Python 标准库的 weakref 是解法:
- 用
weakref.WeakSet()替代set(),自动清理已销毁的对象 - 如果需要传多个参数,别把逻辑塞进
notify,让观察者自己从event对象取字段,或用functools.partial封装 -
WeakSet不支持函数(如lambda),纯函数观察者得单独处理,或改用weakref.ref手动管理
真正麻烦的是跨线程通知和事件类型分发——这两个点一旦加进去,就不再是“简单”结构了,得引入队列或信号机制,别硬塞进这个基类里。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











