为什么新样式类和旧样式类在这种情况下有不同的行为?

时间:2015-04-08 09:50:54

标签: python python-3.x python-2.x python-internals

我发现了一些有趣的内容,这里有一段代码:

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,但我认为答案不够明确。

1 个答案:

答案 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,