我找到了一些有趣的东西,这里有一段代码:
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:
不能保证 德尔()方法被称为对象
当解释器退出时仍然存在。
然后,我猜是因为那个 B.a
仍被引用(由类引用 B
)当翻译存在时。
所以,我添加了一个 del B
在解释器手动存在之前,然后我找到了 a.__del__()
被称为。
现在,我对此感到有些困惑。为什么是 a.__del__()
使用旧样式时调用?为什么新旧风格的行为有不同的行为?
我发现了一个类似的问题 这里,但我认为答案不够明确。
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()
同 B
作为上面的新式类,输出是
A init
About to execute gc.collect()
A del
同 B
作为旧式班(class B:
),输出是
A init
A del
About to execute gc.collect()
也就是说,新风格的类只在之后被删除了 gc.collect()
即使最后一次外部参考已被删除;但旧式的课程立刻被删除了。
其中大部分已经存在 固定 在 Python 3.4: 谢谢 PEP 442,其中包括 基于GC代码的模块关闭程序。现在即使在解释器退出时,模块全局变量也使用普通垃圾收集来完成。如果在Python 3.4下运行程序,程序将打印出来
A init
A del
而Python <= 3.3将打印
A init
(请注意 其他实现仍然可能执行或不执行 __del__
此时,无论它们的版本高于,等于或低于,3.4)