python对象销毁由垃圾回收器决定,__del__不可靠;应优先用with语句、显式close()或weakref.finalize;循环引用需weakref避免泄漏;大对象及时del+gc.collect(0);__slots__和对象池可减少内存开销。

对象什么时候被销毁:别依赖 __del__
Python 对象不是“用完就立刻消失”,它的销毁由垃圾回收器(GC)决定,而 __del__ 方法只是个不可靠的钩子——它可能根本不执行,或在解释器退出时才触发,甚至在多线程下行为不确定。
常见错误现象:__del__ 里做文件关闭、网络连接释放,结果程序跑着跑着就报 ValueError: I/O operation on closed file 或连接泄漏。
- 真正可控的清理方式是上下文管理器(
with语句),配合__enter__/__exit__ - 若必须手动释放资源,显式调用自定义的
close()、shutdown()方法,并在文档里写清楚“需用户主动调用” -
weakref.finalize比__del__更可靠,适合注册轻量级清理回调,但依然不保证执行时机
循环引用导致内存不释放:gc.collect() 不是万能解药
当两个对象互相持有对方的强引用(比如父对象存子对象,子对象又存回父对象的 parent 属性),引用计数不会归零,CPython 的引用计数机制就失效了,得靠周期性运行的 GC 来检测和清理。
使用场景:树形结构、观察者模式、缓存容器中嵌套回调等。
立即学习“Python免费学习笔记(深入)”;
- 默认 GC 是开启的,但你不能假设它“马上”工作;调试时可手动调用
gc.collect()强制触发,但生产环境别这么干 - 用
gc.get_referrers(obj)查谁还引用着这个对象,比瞎猜快得多 - 更治本的办法是用
weakref.ref替代强引用(比如把self.parent改成self._parent_ref = weakref.ref(parent)) - 注意:
__del__和循环引用一起用,会让 GC 完全放弃该对象,造成永久泄漏
大对象提前释放:别等 GC,主动 del + gc.collect()(仅限必要场景)
加载一个几百 MB 的 pandas.DataFrame 或图像数组后,如果后续逻辑不再需要它,光靠“让它出作用域”不够快——局部变量还在栈上,引用没断,GC 可能迟迟不动手。
性能影响:尤其在内存紧张的批处理、Web 请求生命周期中,延迟释放会直接拖慢吞吐、触发频繁 GC、甚至 OOM。
- 明确不再需要时,用
del big_data切断名字到对象的绑定 - 紧接着调用
gc.collect(0)(只清理第 0 代),避免全局扫描开销 - 这不是常规操作,只适用于已确认是瓶颈的大内存对象(比如单次处理 >50MB 的临时数据)
- 别对小对象这么做——
del反而增加字节码开销,得不偿失
类实例太多?检查 __slots__ 和对象复用
每创建一个类实例,Python 默认会给它配一个 __dict__ 字典来存属性,哪怕你只用三四个固定字段,也要多占几百字节。十万实例就是上百 MB 白白浪费。
兼容性影响:加了 __slots__ 后,实例不能再动态赋值新属性(比如 obj.new_field = 1 会报 AttributeError),也不能被 vars() 或 __dict__ 访问。
- 在定义明确、属性固定的类上加
__slots__ = ('id', 'name', 'status') - 如果真需要动态属性,用
__dict__显式管理,而不是默认放任 - 高频创建/销毁的小对象(如事件、DTO),考虑对象池(
queue.LifoQueue配合get/put)或重用已有实例的reset()方法
最常被忽略的一点:内存问题往往不是“对象没释放”,而是“不该创建那么多对象”。先用 objgraph.show_most_common_types() 看堆里到底啥最多,再决定是修引用、加 __slots__,还是重构数据结构。









