weakref仅在双向引用且需避免父对象因子对象存活而无法回收时使用;单向引用、临时对象或生命周期一致的组合不应使用,否则增加理解成本和空引用风险。

weakref 什么时候该用,什么时候不该用
当对象之间存在双向引用(比如父容器持子对象引用,子对象又通过 parent 属性反向持有父引用),且你明确不希望父对象因子对象存活而无法被回收时,weakref 才是合理选择。它不是通用“防泄漏”工具——对单向引用、临时对象、或生命周期天然一致的组合,强行加 weakref 反而增加理解成本和空引用风险。
用 weakref.ref 还是 weakref.WeakKeyDictionary?
取决于引用关系类型:
- 单个对象需弱引用:用
weakref.ref,调用后返回可调用对象,必须先检查是否还活着再解引用:parent_ref = weakref.ref(parent) if parent_ref() is not None: parent_ref().do_something() - 多个子对象共用一个父对象,且父对象作字典 key:用
weakref.WeakKeyDictionary,key 被回收后自动剔除条目,避免手动清理;但 value 仍是强引用,别误以为 value 也会被弱化 - 需要以子对象为 key、父对象为 value:用
weakref.WeakValueDictionary,value 被回收时条目自动消失
常见踩坑:回调函数里直接捕获强引用
注册回调(如信号、事件监听)时,若在 lambda 或闭包中直接引用了 self 或其他长生命周期对象,即使外部用了 weakref,闭包仍会制造隐式强引用链。正确做法是回调内只存弱引用并显式判空:
def make_callback(obj):
obj_ref = weakref.ref(obj)
def callback():
o = obj_ref()
if o is not None:
o.handle_event()
return callback
错误示例(强引用逃逸):
lambda: self.on_event() ← self 被闭包强持有
替代方案比 weakref 更简单的情况
不是所有循环引用都需要 weakref:
立即学习“Python免费学习笔记(深入)”;
- 使用
__slots__减少实例字典开销,配合明确的del或置None清理(如树节点的parent字段设为None) - 用上下文管理器(
__enter__/__exit__)控制生命周期,避免跨作用域持有 - Python 3.4+ 的
gc.collect()能处理大多数可达循环(仅含容器对象),无需干预;只有涉及__del__或 C 扩展时,才真正需要弱引用破环
真正难处理的是那些混入了自定义 __del__、或嵌套了 C API 对象的循环——这时 weakref 不是银弹,得配合 gc 调试工具定位哪一环没断开。










