我正在尝试了解CPython垃圾收集器的内部结构,特别是在调用析构函数时。到目前为止,这种行为很直观,但以下案例让我感到震惊:
我认为只有启用了垃圾收集器才会发生这种情况。有人可以解释为什么会这样吗?有没有办法推迟调用析构函数?
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。
答案 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)