Python明确不回收含__del__的循环引用对象,因无法安全执行析构顺序;gc.collect()默认只清第0代且跳过gc.garbage中对象;weakref.ref可破环,但禁与__del__共存;推荐用with或显式close替代__del__。

循环引用触发 GC 失败的核心原因:__del__ 让 GC 主动放弃回收
不是 GC “能力不足”,而是 Python 明确选择不回收含 __del__ 的循环引用对象。因为一旦进入循环,GC 无法安全决定哪个对象的 __del__ 先执行——若 A 的 __del__ 访问了已被销毁的 B,就会崩溃。所以它把这类对象塞进 gc.garbage 列表,彻底搁置,等你手动处理。
gc.collect() 为什么有时“没用”?代数、阈值和 __del__ 的三重限制
调用 gc.collect() 不等于清空所有垃圾:
- 默认只回收第 0 代(新对象),深层循环引用常滞留在第 2 代,需显式调用
gc.collect(2) - 若对象在
gc.garbage中(即含__del__的循环),gc.collect()完全跳过它们,无论哪一代 - 自动触发依赖阈值(如
gc.get_threshold()返回(700, 10, 10)),小循环可能压根不触发 GC 扫描
weakref.ref 是最轻量且可靠的破环手段
用弱引用替代强引用,从根源上消除循环,让引用计数能自然归零:
- 对非拥有关系(如缓存、观察者、父/子节点中的“反向指针”)一律用
weakref.ref(obj)或weakref.WeakKeyDictionary - 使用时必须加括号调用:
obj = ref(),结果可能是None,需判空 - 不要和
__del__共存:含__del__的对象即使被弱引用指向,其循环端仍卡在gc.garbage,弱引用无法“唤醒”回收
__del__ 本身是高风险设计,多数场景该换掉
官方文档明确建议避免 __del__。真正需要资源清理时:
立即学习“Python免费学习笔记(深入)”;
- 优先用上下文管理器:
with Resource() as r:→ 自动走__exit__ - 显式生命周期控制:
r.close()或r.shutdown(),比依赖不可控的销毁时机更可靠 - 若真要保留
__del__,必须确保类实例不参与任何循环引用——包括闭包、信号连接、全局注册表等隐式强引用
最容易被忽略的是:你以为删掉了所有变量名,但某个模块级字典、日志 handler、或第三方库的内部缓存,可能还持有着你的对象。用 gc.get_referrers(obj) 查一次,往往就暴露问题所在。


















