weakref能解决两个对象互相强引用导致的循环引用问题,如观察者模式、树形结构父子双向指针等场景;通过weakref.ref弱持有反向引用,不增加引用计数,使对象在无其他强引用时可被gc及时回收。

weakref能解决什么循环引用问题
当两个对象互相持有对方的强引用时,Python的引用计数器无法降为0,即使它们已不再被外部使用,GC也难以及时回收——weakref提供一种不增加引用计数的“弱持有”方式,让对象在无其他强引用时可被正常释放。
典型场景包括:观察者模式中回调绑定、缓存容器里存储实例、树形结构中父子双向指针等。比如一个Parent持有一个Child的强引用,而Child又通过parent属性反向引用Parent,就构成经典循环引用。
用weakref.ref替代直接引用
把强引用替换成weakref.ref对象,调用时需显式加()解包获取原对象(可能返回None)。
- 不要写
self.parent = parent,改用self._parent_ref = weakref.ref(parent) - 访问时写
parent = self._parent_ref(),并检查是否为None(对象已被回收) - 避免在
__del__中调用self._parent_ref()——此时引用可能已失效,且__del__执行时机不确定 -
weakref.ref本身不参与循环引用,但要注意它不能包装不可哈希类型(如列表、字典)作为键用于WeakKeyDictionary
用weakref.WeakKeyDictionary管理回调或映射关系
比手动维护ref更安全,适合“以对象为键”的场景,比如事件监听器注册表。
- 键必须是可弱引用的对象(实例、函数等),不能是
int、str、tuple等不可变内置类型 - 值可以是任意类型,但不会影响键的生命周期
- 示例:
callbacks = weakref.WeakKeyDictionary();注册时callbacks[obj] = handler;obj被删后,该键值对自动消失 - 遍历前建议先转成
list(callbacks.keys()),否则迭代中对象被回收可能引发RuntimeError
哪些情况weakref帮不上忙
weakref只破除“引用计数不为0”这一层障碍,不解决所有内存泄漏。
- 全局变量、模块级缓存、日志记录器长期持有对象 → 清理这些强引用才是关键
- 线程局部存储(
threading.local)或未清理的atexit钩子 → 这些会阻止对象回收,和weakref无关 - 使用
__slots__但忘了把弱引用字段加进去 → 会导致AttributeError,需在__slots__中显式包含_parent_ref等弱引用名 - C扩展中绕过Python引用计数机制的指针(如NumPy数组的
base)→weakref无法感知这类C层引用
真正难排查的是混合了弱引用与C层/线程/全局状态的泄漏,这时候得靠gc.get_referrers()和objgraph定位谁还在强持有它。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











