用weakset实现观察者模式可避免内存泄漏与迭代异常:注册自动去重,通知时天然线程安全且支持同步/异步混合调度,配合detach_all()和has_observers()提升可维护性。

用类方法实现观察者模式,关键不在“能不能写”,而在于如何避免常见陷阱——比如迭代时修改列表、强引用导致内存泄漏、同步/异步混用阻塞事件循环。真正优雅的做法,是让注册、通知、清理三个环节都具备鲁棒性和扩展性。
用 WeakSet 管理观察者,自动解引用
不要用 list 或 set 直接存 observer 实例。Python 的 weakref.WeakSet 是更优选择:它持有对象的弱引用,当某个观察者被垃圾回收(比如局部对象退出作用域、GUI 组件销毁),WeakSet 会自动剔除它,无需手动 detach。
- 初始化时:self._observers = WeakSet()
- 注册时:self._observers.add(observer),重复添加自动去重
- 通知前无需 copy,WeakSet 迭代安全,且天然规避循环引用风险
统一 update 接口,支持灵活传参
观察者方法签名应为 update(self, event: str, /, **data),而非固定参数或仅 *args。这样既兼容事件类型区分(如 "order_created"、"inventory_updated"),又便于传递结构化上下文(如 order_id、user_id、timestamp)。
- 主题 notify 时调用:for obs in self._observers: obs.update("order_paid", order_id=1001, amount=299.0)
- 具体观察者可按需提取字段,不关心其他参数,也不依赖位置顺序
- 避免用 lambda 或闭包注册(易捕获 subject 引用),改用普通方法或显式绑定实例
通知逻辑分离,不阻塞主线程
类方法中的 notify 不应直接执行耗时操作。尤其在 asyncio 环境下,要主动区分协程与普通函数:
- 用 inspect.iscoroutinefunction 判断 observer.update 是否为协程
- 协程观察者:用 asyncio.create_task(obs.update(...)) 调度,fire-and-forget
- 普通观察者:同步调用,不 await,不拖慢事件分发
- 整个 notify 方法保持同步、无 await,确保它可在任意上下文安全调用
提供显式生命周期钩子
优雅不止于运行时,也体现在可维护性。在 Subject 类中补充两个辅助方法:
- detach_all():清空 WeakSet,用于 subject 销毁前释放资源
- has_observers():返回 len(self._observers) > 0,方便做短路逻辑(例如无监听时不发日志)
- 可选增加 attach_once(event_name, callback),内部包装为一次性观察者,触发后自动移除











