我发现了一些有趣的内容,这里有一段代码:
class A(object):
def __init__(self):
print "A init"
def __del__(self):
print "A del"
class B(object):
a = A()
如果我运行此代码,我会得到:
A init
但如果我将class B(object)
更改为class B()
,我会得到:
A init
A del
我在__del__ doc中找到了一条说明:
无法保证为对象调用 del ()方法 解释器退出时仍然存在。
然后,我想这是因为当解释器存在时,B.a
仍被引用(由类B
引用)。
所以,我在手动解释存在之前添加了del B
,然后我发现a.__del__()
被调用了。
现在,我对此感到有些困惑。为什么在使用旧样式类时调用a.__del__()
?为什么新旧风格的行为有不同的行为?
我发现了一个类似的问题here,但我认为答案不够明确。
答案 0 :(得分:11)
TL; DR:这是CPython中的old issue,最终在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 documentation允许这样做:
无法保证在解释器退出时仍然存在的对象调用
__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()
如上所述,B
为新式类,输出为
A init
About to execute gc.collect()
A del
将B
作为旧式类(class B:
),输出为
A init
A del
About to execute gc.collect()
也就是说,新样式类仅在gc.collect()
之后被删除,即使最后一次外部引用已被删除;但旧式的课程立刻被删除了。
fixed中的大部分已经Python 3.4:感谢PEP 442,其中包括module shutdown procedure based on GC code。现在即使在解释器退出时,模块全局变量也使用普通的垃圾收集来完成。如果在Python 3.4下运行程序,程序将打印
A init
A del
而使用Python< = 3.3,它将打印
A init
(请注意>此时其他实施仍可能执行或不执行__del__
,无论其版本高于,高于或低于3.4,