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

时间:2010-04-05 11:28:15

标签: python garbage-collection cpython

我正在尝试了解CPython垃圾收集器的内部结构,特别是在调用析构函数时。到目前为止,这种行为很直观,但以下案例让我感到震惊:

  1. 禁用GC。
  2. 创建一个对象,然后删除对它的引用。
  3. 对象被销毁,_____del_____方法被调用。
  4. 我认为只有启用了垃圾收集器才会发生这种情况。有人可以解释为什么会这样吗?有没有办法推迟调用析构函数?

    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。

3 个答案:

答案 0 :(得分:9)

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

由于在ref = None之后没有任何参考文件留给您的对象,因此其__del__方法因其引用计数变为零而被调用。

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

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

def __init__(self):
    self.myself = self

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

答案 1 :(得分:4)

根据您对垃圾收集器的定义,CPython有两个垃圾收集器,引用计数一个,另一个垃圾收集器。
参考计数器始终有效,无法关闭,因为它非常快速且轻便,不会对系统的运行时间产生显着影响。
另一个(我认为有一些标记和扫描的变量)会经常运行,并且可以被禁用。这是因为它需要解释器在运行时暂停,这可能发生在错误的时刻,并消耗相当多的CPU时间。
这种禁用它的能力存在于你期望做出时间关键的事情的时候,并且缺少这个GC不会给你带来任何问题。

答案 2 :(得分:4)

文档here解释了所谓的“可选垃圾收集器”实际上是 cyclic 垃圾的收集器(引用计数不会捕获的那种)。解释引用计数here,并对其与循环gc:

的相互作用表示赞同
  

虽然Python使用传统的   它的引用计数实现   还提供了一个循环探测器   用于检测参考周期。这个   允许应用程序不用担心   创建直接或间接循环   引用;这些都是弱点   垃圾收集使用   只是引用计数。参考   周期由对象组成   包含(可能是间接的)引用   对自己,让每个对象都在   循环有一个参考计数   是非零的。典型参考   计数实现不能   收回属于任何人的记忆   参考周期中的对象,或   从中的对象引用   循环,即使没有   进一步参考循环   本身。