如果我在Python中加载模块,它是否会被垃圾收集?构建这个问题的另一种方法是,Python在哪里保留对Python模块的引用?我假设如果没有任何引用,垃圾收集器将删除一个模块。
这是我在Python解释器中尝试过的一个例子:
T
输出:
真
因此,让我们删除脚本中对模块的引用。
>>> from importlib import import_module
>>> import sys
>>> import gc
>>> x = import_module('math')
>>> 'math' in sys.modules
Python仍然跟踪数学模块,因为输出仍然是:
真
但是现在如果我从>>> del x
>>> gc.collect()
>>> 'math' in sys.modules
删除数学,我不再知道任何进一步的参考:
sys.modules
然而,>>> del sys.modules['math']
>>> gc.collect()
的输出是:
0
没有收集任何垃圾,因此该模块不再位于gc.collect()
或我的脚本中。为什么不收集垃圾?
答案 0 :(得分:1)
通常,至少在3.4及更高版本中,模块对象在这方面不应该是特殊的。当然,通常会在sys.modules
中引用每个已加载的模块,但如果您已明确删除它,则模块应该能够消失。
话虽如此,过去肯定存在一些问题,以防止在某些情况下发生这种情况,我不会保证3.7中没有任何此类问题。
不幸的是,您的测试实际上并未测试任何内容。大概你正在使用CPython。在CPython中,垃圾收集器使用引用计数 - 它直接在每个对象上存储一个计数,每次绑定一个新名称时递增和递减计数,如果计数变为0则立即删除它。{{{ 1}}模块是一个循环收集器,它需要处理一些特殊情况,其中两个(或更多)对象相互引用但没有其他人引用它们。如果模块不是这样一个循环的一部分,它将在你调用gc
之前被删除,所以当然会返回0.但是那个0什么也没告诉你。
您的测试还有其他问题。
首先,您不应该在交互式解释器中测试垃圾。所有类型的额外东西都被保存在那里,其解释方式很复杂。编写测试脚本要好得多。
其次,您不应该使用gc.collect()
作为测试。它是一个扩展模块(即用C语言而不是Python语言编写),即使在3.5中发生重大变化之后,它们仍然无法正常工作。它也是一个核心模块,可能是启动的一部分,也可能是解释器其他部分所需要的,即使您没有从代码中引用它。所以,使用其他东西要好得多。
无论如何,我认为可能有一种方法可以直接测试它,而不使用调试器,但没有任何关于它是否可行的承诺。
首先,您需要创建一个math
的子类,它具有打印出一些消息的types.ModuleType
方法。然后,您只需要导入一个模块(.py,而不是扩展模块)并将其__del__
设置为该子类。这可能与.py文件中的__class__
一样简单。现在,当它被收集时,它的析构函数将会运行,并且你将证明它已被收集。 (好吧,证明它是收集的,除非析构函数恢复了它,但是如果你的析构函数除了打印一个静态字符串之外什么都不做,那么希望不用担心。)
答案 1 :(得分:1)
根据abarnert的回答,我创建了以下自运行示例,演示了我试图理解的行为:
debugPrint
按原样运行输出:
我被删除了
精加工
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')
行的输出已注释掉:
精加工
我被删除了
很明显,当所有对它们的引用都被删除时,模块是垃圾收集的,除非有问题的模块有些特殊,否则当应用程序和del sys.modules['urllib3']
中的引用有已被删除。