TL;DR:这是一个老问题在 CPython 中,这个问题最终被修复了CPython 3.4。在 3.4 之前的 CPython 版本中,由模块全局变量引用的引用循环保持活动状态的对象在解释器退出时未正确完成。新式类在其内部具有隐式循环type
实例;旧式类(类型classobj
)没有隐式引用循环。
尽管在这种情况下已修复,CPython 3.4 文档仍然建议不要依赖__del__口译员退出时被叫到 - 认为自己受到警告。
新样式类本身具有引用循环:最值得注意的是
>>> class A(object):
... pass
>>> A.__mro__[0] is A
True
这意味着它们不能立即删除*,只能在垃圾收集器运行时删除。由于对它们的引用由主模块保存,因此它们将保留在内存中直到解释器关闭。最后,在模块清理期间,main 中的所有模块全局名称都被设置为指向None
,并且引用计数减少到零的任何对象(例如您的旧式类)也会被删除。然而,具有引用循环的新型类不会因此被发布/最终确定。
循环垃圾收集器不会在解释器出口处运行(这是由CPython 文档:
不保证__del__()
为解释器退出时仍然存在的对象调用方法。
现在,Python 2 中的旧式类没有隐式循环。当 CPython 模块清理/关闭代码将全局变量设置为None
,对类的唯一剩余引用B
被丢弃;然后B
已删除,最后引用a
被丢弃,并且a
也已敲定。
为了证明新式类有循环并且需要 GC 扫描,而旧式类则不需要,您可以在 CPython 2 中尝试以下程序(CPython 3 不再有旧式类):
import gc
class A(object):
def __init__(self):
print("A init")
def __del__(self):
print("A del")
class B(object):
a = A()
del B
print("About to execute gc.collect()")
gc.collect()
With B
作为上面的新式类,输出是
A init
About to execute gc.collect()
A del
With B
作为旧式类(class B:
),输出为
A init
A del
About to execute gc.collect()
也就是说,new-style类在之后才被删除gc.collect()
尽管最后一次外部引用它已经被删除了;但旧式类立即被删除。
其中大部分已经fixed in Python 3.4: 谢谢PEP 442,其中包括基于GC代码的模块关闭程序。现在,即使在解释器退出时,模块全局变量也会使用普通垃圾收集来完成。如果你在Python 3.4下运行你的程序,程序将打印
A init
A del
而对于 Python
A init
(Do note其他实现仍然可能会或可能不会执行__del__
此时,无论它们的版本高于、等于或低于 3.4)