我发现了一些有趣的东西,这是一段代码:

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__()?为什么新旧样式类具有不同的行为?

我发现了类似的问题here,但我认为答案还不够清楚。

最佳答案

TL; DR:这是CPython中的old issue,最终在CPython 3.4中修复。在模块早于3.4的CPython版本中,在模块全局变量引用的引用循环中保留的对象在解释器退出时未正确完成。新样式的类在其type实例中具有隐式循环;老式类(类型classobj)没有隐式引用周期。

即使在这种情况下已修复,CPy​​thon 3.4文档仍然建议不要依赖于在解释器导出上调用 __del__ -考虑一下自己。

新样式类本身具有引用循环:最值得注意的是

>>> class A(object):
...     pass
>>> A.__mro__[0] is A
True

这意味着它们不能立即删除*,而只能在运行垃圾收集器时删除。由于主模块保留了对它们的引用,因此它们将保留在内存中,直到解释器关闭为止。最后,在模块清理期间,主模块中的所有模块全局名称均设置为指向None,并且引用计数减少为零的任何对象(例如,您的旧类)也都被删除。但是,具有引用循环的新型类不会因此而被释放/定型。

循环垃圾收集器不会在解释器导出运行(CPython documentation允许:



现在,Python 2中的旧类没有隐式循环。当CPython模块的清除/关闭代码将全局变量设置为None时,将删除对B类的唯一剩余引用;否则,将仅删除对B类的引用。然后删除a,删除对a的最后一个引用,并最终确定B

为了证明新样式类具有循环并需要进行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

使用class B:作为旧类(gc.collect()),输出为
A init
A del
About to execute gc.collect()

也就是说,即使已经删除了最后一个外部引用,该新样式类也仅在__del__之后删除;但是旧类立即被删除。

其中很多已经是fixed中的Python 3.4了:多亏了PEP 442,其中包括module shutdown procedure based on GC code。现在,即使在解释器退出时,也可以使用普通的垃圾收集来最终确定模块的全局变量。如果您在Python 3.4下运行程序,该程序将打印
A init
A del

而对于Python
A init

(,请注意,其他实现此时仍可能执行或可能不执行ojit_code,而不管它们的版本高于,等于或低于3.4)。

关于python - 在这种情况下,为什么新样式类和旧样式类具有不同的行为?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29511332/

10-14 01:08