7

我试图了解 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()

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

4

3 回答 3

11

Python既有引用计数垃圾回收,又有循环垃圾回收,gc模块控制的是后者。引用计数不能被禁用,因此当循环垃圾收集器关闭时仍然会发生。

由于在 之后没有对您的对象的引用,因此由于其引用计数变为零而调用ref = None其方法。__del__

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

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

def __init__(self):
    self.myself = self

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

于 2010-04-05T11:31:07.500 回答
5

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

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

于 2010-04-05T14:11:59.753 回答
4

根据您对垃圾收集器的定义,CPython 有两个垃圾收集器,一个是引用计数,一个是另一个。
参考计数器一直在工作,并且无法关闭,因为它是一种非常快速且轻量级的计数器,不会显着影响系统的运行时间。
另一个(我认为是各种标记和扫描)经常运行,并且可以禁用。这是因为它要求解释器在运行时暂停,这可能发生在错误的时刻,并且会消耗大量的 CPU 时间。
当您希望做一些时间紧迫的事情时,这种禁用它的能力就在那里,并且缺少这个 GC 不会给您带来任何问题。

于 2010-04-05T11:38:38.180 回答