Python模块是否被垃圾收集?

Are Python modules ever garbage collected?

如果我在python中加载一个模块,它是否会被垃圾收集?构建这个问题的另一种方法是,Python在哪里保存对Python模块的引用?正如我假设的那样,如果不再有任何引用,垃圾收集器将删除一个模块。

下面是我在in python解释器中尝试的一个示例:

1
2
3
4
5
6
>>> from importlib import import_module
>>> import sys
>>> import gc

>>> x = import_module('math')
>>> 'math' in sys.modules

此输出:

True

因此,让我们删除对脚本中模块的引用。

1
2
3
>>> del x
>>> gc.collect()
>>> 'math' in sys.modules

python仍然跟踪数学模块,因为输出仍然是:

True

但是现在,如果我从sys.modules中删除math,我将不再知道任何进一步的引用:

1
2
>>> del sys.modules['math']
>>> gc.collect()

但是,gc.collect()的输出是:

0

没有垃圾收集到任何内容,因此该模块不再位于sys.modules或我的脚本中。为什么不收集垃圾?


基于Abarnett的答案,我创建了下面的自我运行示例,演示了我试图理解的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from types import ModuleType
from importlib import import_module
import sys

class MyModule(ModuleType):
    def __del__(self):
        print('I am being deleted')

if __name__ == '__main__':
    x = import_module('urllib3')
    x.__class__ = MyModule
    del x
    del sys.modules['urllib3'] # Comment this out and urllib3 will NOT be garbage collected before the script finishes
    print('finishing')

按原样运行时的输出:

I am being deleted

finishing

del sys.modules['urllib3']行注释的输出:

finishing

I am being deleted

很明显,当所有对模块的引用都被删除时,模块会像人们预期的那样被垃圾收集,并且除非所讨论的模块有点特殊,否则当应用程序和sys.modules中的引用被删除时,就会发生这种情况。


一般来说,至少在3.4和更高版本中,模块对象在这方面不应该是任何特殊的。当然,通常在sys.modules中有一个对每个加载模块的引用,但是如果您已经明确删除了它,那么一个模块应该能够离开。

这就是说,过去肯定有一些问题在某些情况下阻止了这种情况的发生,而且我不会保证在3.7之后不会有任何这样的问题。

不幸的是,您的测试实际上并没有测试任何东西。大概你用的是cpython。在cpython中,垃圾收集器使用引用计数,它直接在每个对象上存储一个计数,每次新名称绑定到该对象时递增和递减计数,如果该计数变为0,则立即将其删除。gc模块中的东西是一个循环收集器,它需要处理一些特殊情况,其中两个(或更多)对象彼此引用,但没有其他对象引用它们。如果模块不是这样一个循环的一部分,那么在您调用gc.collect()之前,它将被删除,因此当然会返回0。但是0什么也不告诉你。

你的考试还有其他问题。

首先,您不应该在交互式解释器中测试垃圾。各种各样的额外的东西都保存在那里,以复杂的方式解释。编写一个测试脚本要好得多。

第二,你不应该使用math作为你的测试。它是一个扩展模块(也就是说,用C而不是python编写),即使在3.5中进行了重大更改之后,它们仍然不能正常工作。它也是一个核心模块,可能是启动的一部分,或者是解释器的其他部分所需要的,即使您没有从代码中引用它。所以,最好用点别的。

不管怎样,我认为可能有一种方法可以直接测试它,而不使用调试程序,但是对于它是否工作没有任何承诺。

首先,您需要创建types.ModuleType的一个子类,它有一个__del__方法,可以打印出一些消息。然后,您只需要导入一个模块(一个.py模块,而不是扩展模块),并将它的__class__设置为该子类。这可能和.py文件中的__class__ = MyModuleSubclass一样简单。现在,当它被收集时,它的析构函数将运行,并且您将有证据证明它是被收集的。(好吧,除非析构函数恢复了它,否则证明它是被收集的,但是如果析构函数除了打印一个静态字符串之外什么都不做,那就不必担心了。)