__del__不执行主因是引用未释放,常见于异常帧持有或循环引用;应优先用显式close()或上下文管理器替代,而非依赖不可靠的__del__。

__del__ 方法不执行,绝大多数时候不是你写错了,而是对象还被谁“拽着”——引用计数没降到 0,GC 就不会碰它。
del obj 后 __del__ 没触发?先查谁还在引用它
执行 del obj 只是删掉一个名字,不等于对象立刻消失。真正决定是否调用 __del__ 的,是它的引用计数是否真归零。
- 用
sys.getrefcount(obj)查当前引用数(注意:传参本身会 +1,结果要减 1 才准) - 更直接的是
gc.get_referrers(obj)—— 它能列出所有持有该对象引用的对象 - 如果返回里有
<frame at 0x...>,说明某个栈帧还在强引用它,常见于异常发生后未退出的函数作用域 - 其他常见“拽着不放”的地方:
mock.Mock(side_effect=[Exception])、闭包变量、全局缓存、日志 handler、threading.local()实例
mock 测试中 __del__ 失效:帧引用陷阱最隐蔽
这是单元测试里最高频的失效场景。只要你在 try/except 块里用 mock.Mock(side_effect=[SomeError]),哪怕异常被捕获了,当前函数帧仍会把 self 锁在 f_locals 里。
- 现象:
del obj后__del__不执行,flags不变,gc.get_referrers(obj)返回一个<frame> - 原因:Python 在异常发生时,会把当前帧的局部变量(含
self)、异常对象、traceback 全部打包进帧对象,直到该帧退出才释放 - 修复(Python 3.12+):
sys.exc_info()触发一次,再手动清理:del sys.last_traceback, sys.last_type, sys.last_value - 兼容旧版更稳妥的做法:避免在测试中让异常帧长期存活,改用
with self.assertRaises(...)或提前退出函数作用域
循环引用导致 __del__ 永远不调用
两个对象互相持引用(比如 A 有 B 的实例,B 又存了 A 的引用),即使你 del a 和 del b,它们的引用计数也不会归零,__del__ 就永远不会被触发。
立即学习“Python免费学习笔记(深入)”;
- GC 虽能最终回收循环引用,但不保证何时运行,也不保证按什么顺序调用
__del__ - 更危险的是:若
__del__中访问另一个循环引用对象,可能因对方已被提前销毁而抛AttributeError - 解法不是等 GC,而是从设计上打破强引用:用
weakref.ref()替代直接持有,或显式调用obj.cleanup()解除关联 - 验证是否存在循环引用:
gc.collect()后检查gc.garbage是否包含你的对象
别把关键资源清理逻辑塞进 __del__
__del__ 的调用时机完全不可控,且可能根本不执行。它不适合做任何关键操作。
- 以下情况
__del__根本不会运行:os._exit()强制终止、解释器崩溃、主线程退出而子线程仍持引用、程序退出时模块已卸载 - 文件句柄、数据库连接、锁、socket 等必须靠显式控制:
close()、__enter__/__exit__、with open(...) - 如果真要用
__del__,只限于调试计数、日志标记、非关键状态清除等“尽力而为”场景 - 务必避开副作用:不要访问模块级变量、不要抛异常、不要在其中创建新强引用(否则对象永远无法回收)
真正难的不是写 __del__,而是意识到它根本不能当“析构函数”用;最容易忽略的,是你以为已经 del 掉的对象,其实正被某个帧、缓存或 mock 实例悄悄攥着。


















