Python中用weakref+WeakSet可安全实现观察者模式,避免因强引用导致的内存泄漏;需注意bound method不可弱引用、lambda无法被WeakSet存储、异步通知需区分协程与普通函数。

Python 里用 weakref + 可调用对象列表就能稳妥实现观察者模式,不需要第三方库,但手动管理订阅生命周期是关键。
为什么不能直接用 list 存强引用 observer?
常见错误是把回调函数或实例方法直接 append 进 observers 列表,结果导致被观察对象生命周期被意外延长,甚至引发内存泄漏——尤其是 observer 是某个 GUI 窗口或长生命周期类的 bound method 时。
-
self.observers.append(callback)→ callback 持有对 self 的引用 → 被观察对象无法被 gc - observer 实例销毁后,其回调仍留在列表中 → 下次通知触发
AttributeError或静默失败 - Python 的 bound method 是可哈希但不可 weakref 的,需用
weakref.WeakKeyDictionary或封装成弱引用可调用对象
如何安全注册和通知 observer?
核心是把 observer 包装成可弱引用的 callable,并在通知前自动过滤已销毁对象。推荐用 weakref.WeakSet 存 observer 实例,或用 weakref.ref 包裹单个 callable:
import weakref
<p>class Subject:
def <strong>init</strong>(self):
self._observers = weakref.WeakSet() # 自动清理销毁的 observer 实例</p><pre class='brush:python;toolbar:false;'>def attach(self, observer):
self._observers.add(observer) # observer 必须是 object,不能是普通函数
def notify(self, *args, **kwargs):
# WeakSet 自动跳过已被 gc 的对象
for obs in self._observers:
obs.update(*args, **kwargs) # 假设 observer 有 update 方法- 如果 observer 是函数(非绑定方法),改用
weakref.ref(func)+ 列表存储,每次通知前检查ref() is not None - 避免在
notify中修改_observers(比如 remove),否则可能触发 RuntimeError:dictionary changed size during iteration - 通知顺序不保证,不要依赖执行先后;如需顺序控制,显式排序或用
collections.OrderedDict(配合弱引用包装)
如何处理 lambda 或闭包 observer?
lambda 和闭包无法被 weakref 引用,强行传入 WeakSet 会报 Type Error: cannot create weak reference to 'function' object。
立即学习“Python免费学习笔记(深入)”;
- 改用普通函数名注册,而非
lambda x: print(x) - 若必须用临时逻辑,把它封装进一个短生命周期对象里,再将该对象加入
WeakSet - 更稳妥的做法:统一要求 observer 实现某个接口(如
on_state_change),并作为类实例注册 —— 这样既支持弱引用,也便于类型检查和调试
什么时候该用 asyncio 版本?
当被观察对象的状态变更来自异步 I/O(如 WebSocket 收消息、定时任务更新缓存),且 observer 需要 await 某些操作时,同步通知会阻塞事件循环。
- 不要在
notify里直接await—— 同步方法不能 await - 改用
asyncio.create_task(observer.on_update(...)),但要注意 task 泄漏:没 await 就丢弃 task,异常不会抛出 - 更可控的方式:让 subject 暴露
async_notify,observer 显式声明是否异步(通过检查inspect.iscoroutinefunction),再分发到线程池或 event loop
真正难的不是写 notify 循环,而是厘清谁该持有谁的引用、谁负责清理、以及通知失败时要不要重试或降级 —— 这些得根据具体场景定,没有通用解。弱引用只是起点,不是银弹。


















