weakref 能破除循环引用但非万能,仅当无其他强引用路径时有效;weakref.ref 用于单对象弱引用缓存,WeakKeyDictionary 用于以对象为键的映射;避免在 del 中调用 weakref,应改用 finalize 或显式解绑。

weakref 能不能直接破除循环引用?
能,但只在特定结构下有效——weakref.ref 或 weakref.WeakKeyDictionary 等对象本身不增加引用计数,因此可打断「A 持有 B、B 又持有 A」中的一环。但它不是银弹:若循环里存在非弱引用路径(比如某个对象被全局变量、闭包或 C 扩展强持有),weakref 依然无法释放。
什么时候该用 weakref.ref 而不是 weakref.WeakKeyDictionary?
看持有关系是否「单向依赖」:
- 用
weakref.ref:当你要缓存一个对象的“快照”,又不想阻止它被回收(比如回调函数里保存 self 的弱引用) - 用
weakref.WeakKeyDictionary:当你要做「以对象为键的映射」,且键对象生命周期应独立于字典存在(比如事件监听器注册表,监听器销毁后自动从字典中消失) - 别用
weakref.WeakValueDictionary当键是临时对象——它的值被回收后,键仍留在字典里,可能引发 KeyError 或逻辑错乱
常见错误:在 __del__ 里调用 weakref 对象
__del__ 触发时机不确定,且此时对象可能已处于析构中途,weakref.ref 返回的 callable 可能已失效,调用会返回 None 而不报错,容易掩盖逻辑缺陷:
import weakref
<p>class Parent:
def <strong>init</strong>(self):
self.child = Child(self)</p><p>class Child:
def <strong>init</strong>(self, parent):
self._parent_ref = weakref.ref(parent) # ✅ 弱引用父对象</p><pre class="brush:php;toolbar:false;">def __del__(self):
parent = self._parent_ref() # ⚠️ 可能为 None,且 __del__ 不保证执行
if parent is not None:
print("parent still alive")
立即学习“Python免费学习笔记(深入)”;
更稳妥的做法是显式解绑(如提供 close() 方法),或用 weakref.finalize 替代 __del__:
weakref.finalize(child, lambda: print("child gone"))
为什么用了 weakref 还有内存残留?
三个高频原因:
- 对象被 logging、traceback、threading.local 或 pytest 的 fixture 缓存等隐式强引用
- 使用了
functools.lru_cache且缓存参数含循环链中的对象——缓存项会强持有整个链 - 在 asyncio 中,未取消的 task 或未清理的
asyncio.Queue元素可能间接持有所谓“已释放”的对象 - PyQt/PySide 的信号槽连接默认是强引用,需显式传
type=Qt.WeakConnection
查证时优先用 gc.get_referrers(obj) 看谁还在引用它,而不是只盯着 weakref 用没用对。

















