一、问题本质:为什么 __del__ 不执行?
Python 主要用两种机制回收对象:
1️⃣ 引用计数(Reference Counting)
对象引用数变成 0 → 立刻销毁 → 调用 __del__
2️⃣ 垃圾回收器(GC,处理循环引用)
用于处理这种情况:
a = A()
b = B()
a.b = b
b.a = a
? 这就是循环引用
⚠️ 关键点来了:
? 带有 __del__ 的对象,如果存在循环引用,GC 不会自动回收
原因是:
Python 无法确定 __del__ 的调用顺序(谁先死?)
如果乱调用,可能导致:
访问已销毁对象
程序崩溃
状态不一致
? 所以 Python 直接选择:不回收(保守策略)
二、复现问题(经典例子)
import gc
class A:
def __init__(self):
self.b = None
def __del__(self):
print("A 被销毁")
class B:
def __init__(self):
self.a = None
def __del__(self):
print("B 被销毁")
a = A()
b = B()
a.b = b
b.a = a
del a
del b
gc.collect()
❗你会发现:
? 什么都没打印
说明:
对象没有被释放
__del__ 没执行
内存泄漏了(逻辑上)
三、如何确认这些对象“卡住了”?
import gc
print(gc.garbage)
? 如果开启调试:
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
你会看到无法回收的对象
四、解决方案(重点)
✅ 方案1:避免 __del__(最推荐)
? Python 官方建议:
❗尽量不要使用 __del__
替代方案:
✔ 使用上下文管理器
class Resource:
def __enter__(self):
print("打开资源")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("释放资源")
使用:
with Resource():
pass
? 100%安全释放
✅ 方案2:使用 weakref 打破循环
import weakref
class A:
def __init__(self):
self.b = None
class B:
def __init__(self, a):
self.a = weakref.ref(a) # 弱引用
? 弱引用不会增加引用计数 → 不形成循环
✅ 方案3:手动断开引用
a.b = None
b.a = None
再删除:
del a
del b
✅ 方案4:用 weakref.finalize(现代推荐)
import weakref
class A:
def __init__(self):
weakref.finalize(self, self.cleanup)
def cleanup(self):
print("资源释放")
? 优点:
不依赖 __del__
支持循环引用
更安全
五、总结一句话
? 核心规律:
❗“只要有循环引用 + __del__,GC 就不会回收”
六、实战建议(非常重要)
如果你在做这些场景:
Selenium 自动化
爬虫
文件/网络连接
浏览器驱动
? 千万不要依赖 __del__ 做资源释放
应该用:
with(最佳)
显式 close()
weakref.finalize
七、给你一个改进版本(安全写法)
class SafeA:
def __init__(self):
self.b = None
def close(self):
print("安全释放 A")
class SafeB:
def __init__(self):
self.a = None
def close(self):
print("安全释放 B")
a = SafeA()
b = SafeB()
a.b = b
b.a = a
# 手动释放
a.close()
b.close()
a.b = None
b.a = None
del a
del b