为什么在禁用CPython垃圾收集器时调用析构函数?

Fre*_*rik 6 python garbage-collection cpython

我试图了解CPython垃圾收集器的内部结构,特别是在调用析构函数时.到目前为止,行为是直观的,但以下情况让我感到震惊:

  1. 禁用GC.
  2. 创建一个对象,然后删除对它的引用.
  3. 该对象被销毁并调用_____del_____方法.

我以为只有启用垃圾收集器才会发生这种情况.有人可以解释为什么会这样吗?有没有办法推迟调用析构函数?

import gc
import unittest

_destroyed = False

class MyClass(object):

    def __del__(self):
        global _destroyed
        _destroyed = True

class GarbageCollectionTest(unittest.TestCase):

    def testExplicitGarbageCollection(self):
        gc.disable()
        ref = MyClass()
        ref = None
        # The next test fails. 
        # The object is automatically destroyed even with the collector turned off.
        self.assertFalse(_destroyed) 
        gc.collect()
        self.assertTrue(_destroyed)

if __name__=='__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

免责声明:此代码不适用于生产 - 我已经注意到这是特定于实现的,并且不适用于Jython.

Ric*_*dle 9

Python有引用计数垃圾收集和循环垃圾收集,它是gc模块控制的后者.无法禁用引用计数,因此在循环垃圾收集器关闭时仍会发生.

由于之后没有任何引用留给你的对象ref = None,__del__因此它的引用计数为零时调用它的方法.

文档中有一条线索:"由于收集器补充了Python中已经使用的引用计数......"(我的重点).

您可以通过使对象引用自身来停止第一个断言,因此它的引用计数不会变为零,例如通过给它构造函数:

def __init__(self):
    self.myself = self
Run Code Online (Sandbox Code Playgroud)

但如果你这样做,第二个断言将会触发.那是因为__del__没有收集带方法的垃圾循环- 请参阅gc.garbage的文档.


Ale*_*lli 5

这里的文档(原始链接是一个文档部分,直到 Python 3.5 在这里,后来被重新定位)解释了所谓的“可选垃圾收集器”实际上是一个循环垃圾收集器(引用计数不会捕获)(另请参见此处)。这里解释引用计数,并提到了它与循环的相互作用gc

虽然 Python 使用传统的引用计数实现,但它还提供了一个循环检测器来检测引用循环。这让应用程序不必担心创建直接或间接的循环引用;这些是仅使用引用计数实现的垃圾收集的弱点。引用循环由包含(可能是间接的)对自身的引用的对象组成,因此循环中的每个对象都有一个非零的引用计数。典型的引用计数实现无法回收属于引用循环中任何对象的内存,或从循环中的对象引用的内存,即使没有对循环本身的进一步引用。