为什么在这种情况下新样式类和旧样式类具有不同的行为? [英] Why do new style class and old style class have different behavior in this case?

查看:83
本文介绍了为什么在这种情况下新样式类和旧样式类具有不同的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

A 类(对象):def __init__(self):打印初始化"def __del__(self):打印A del"B类(对象):a = A()

如果我运行这段代码,我会得到:

一个初始化

但是如果我把class B(object)改成class B(),我会得到:

一个初始化德尔

我在 __del__ doc 中发现了一条注释:

<块引用>

不保证为对象调用 del() 方法当解释器退出时仍然存在.

那么,我猜是因为当解释器存在时,B.a 仍然被引用(被类 B 引用).

于是,我在解释器存在之前手动添加了一个del B,然后发现a.__del__()被调用了.

现在,我对此有点困惑.为什么在使用旧样式类时调用 a.__del__() ?为什么新旧样式类的行为不同?

我在这里发现了一个类似的问题,但我认为答案还不够清楚.

解决方案

TL;DR:这是一个老问题 在 CPython 中,最终在 中修复CPython 3.4.在 3.4 之前的 CPython 版本中,由模块全局变量引用的引用循环保持活动状态的对象在解释器退出时未正确完成.新式类在它们的 type 实例中有隐式循环;旧式类(classobj 类型)没有隐式引用循环.

即使在这种情况下已修复,CPy​​thon 3.4 文档仍然建议不要依赖 __del__ 在解释器退出时被调用 - 考虑自己警告.

<小时>

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

<预><代码>>>>A类(对象):... 经过>>>A.__mro__[0] 是 A真的

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

循环垃圾收集器不会在解释器出口运行(CPython 文档:

<块引用>

不保证 __del__() 方法在解释器退出时仍然存在的对象被调用.

<小时>

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

<小时>

为了证明新式类有循环并且需要GC扫描,而旧式类没有,您可以在CPython 2中尝试以下程序(CPython 3没有旧式类任何更多):

导入gcA类(对象):def __init__(self):打印(初始化")def __del__(self):打印(一个德尔")B类(对象):a = A()德尔Bprint("即将执行gc.collect()")gc.collect()

B为新式类,输出为

一个初始化即将执行 gc.collect()德尔

B 作为旧式类(class B:),输出为

一个初始化德尔即将执行 gc.collect()

也就是说,新样式类仅在 gc.collect() 之后才被删除,即使它的最后一个外部引用已经被删除;但是老式的类被立即删除了.

<小时>

其中大部分已经在 修复.org/3.4/whatsnew/3.4.html#pep-442-safe-object-finalization" rel="nofollow">Python 3.4:感谢 PEP 442,其中包括 基于GC代码的模块关闭程序.现在,即使在解释器退出时,模块全局变量也会使用普通垃圾收集完成.如果你在 Python 3.4 下运行你的程序,程序会打印

一个初始化德尔

而使用 Python <=3.3 它将打印

一个初始化

(请注意其他实现此时仍可能执行也可能不执行__del__,无论它们的版本是高于、处于或低于, 3.4)

I found something interesting, here is a snippet of code:

class A(object):
    def __init__(self):
        print "A init"

    def __del__(self):
        print "A del"

class B(object):
    a = A()

If I run this code, I will get:

A init

But if I change class B(object) to class B(), I will get:

A init
A del

I found a note in the __del__ doc:

It is not guaranteed that del() methods are called for objects that still exist when the interpreter exits.

Then, I guess it's because that B.a is still referenced(referenced by class B) when the interpreter exists.

So, I added a del B before the interpreter exists manually, and then I found a.__del__() was called.

Now, I am a little confused about that. Why is a.__del__() called when using old style class? Why do new and old style classes have different behavior?

I found a similar question here, but I think the answers are not clear enough.

解决方案

TL;DR: this is an old issue in CPython, that was finally fixed in CPython 3.4. Objects kept live by reference cycles that are referred to by module globals are not properly finalized on interpreter exit in CPython versions prior to 3.4. New-style classes have implicit cycles in their type instances; old-style classes (of type classobj) do not have implicit reference cycles.

Even though fixed in this case, the CPython 3.4 documentation still recommends to not depend on __del__ being called on interpreter exit - consider yourself warned.


New style classes have reference cycles in themselves: most notably

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

This means that they cannot be deleted instantly*, but only when the garbage collector is run. Since a reference to them is being held by the main module, they will stay in memory until the interpreter shutdown. At the end, during the module clean-up, all the module global names in the main are set to point to None, and whichever objects had their reference counts decreased to zero (your old-style class for example) were also deleted. However, the new-style classes, having reference cycles, would not be released/finalized by this.

The cyclic garbage collector would not be run at the interpreter exit (which is allowed by the CPython documentation:

It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.


Now, old-style classes in Python 2 do not have implicit cycles. When the CPython module cleanup/shutdown code sets the global variables to None, the only remaining reference to class B is dropped; then B is deleted, and the last reference to a is dropped, and a too is finalized.


To demonstrate the fact that the new-style classes have cycles and require a GC sweep, whereas the old-style classes do not, you can try the following program in CPython 2 (CPython 3 does not have old-style classes any more):

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 as new-style class as above, the output is

A init
About to execute gc.collect()
A del

With B as old-style class (class B:), the output is

A init
A del
About to execute gc.collect()

That is, the new-style class was deleted only after gc.collect() even though the last outside reference to it was dropped already; but the old-style class was deleted instantly.


Much of this is already fixed in Python 3.4: thanks to PEP 442, which included the module shutdown procedure based on GC code. Now even on interpreter exit the module globals are finalized using the ordinary garbage collection. If you run your program under Python 3.4, the program will print

A init
A del

Whereas with Python <=3.3 it will print

A init

(Do note that other implementations still might or might not execute __del__ at this moment, regardless of the version of them being above, at, or below, 3.4)

这篇关于为什么在这种情况下新样式类和旧样式类具有不同的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆