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或我的脚本中。为什么不收集垃圾?
- 你怎么知道这不是垃圾收集?当然,它不是你的gc.collect()呼叫收集的,但这可能是因为它是在你从sys.modules中取出后收集的。
- 阿兰·菲是对的。假设您使用的是cpython,它使用refcounting,所以一旦没有引用,对象就会消失。gc.collect调用只需要处理有引用的情况,但只需要在其他未引用对象之间的循环中进行。
- 首先,sys.getrefcount(x)的价值是什么?
- 顺便说一下,我认为在3.4+中,您可以通过创建moduleType的子类来测试这一点,向它添加一个__del__方法,只打印"我被删除了",然后编写一个普通的.py模块,将其__class__设置为该类型,然后导入该模块而不是math。(另外,math一开始是一个很奇怪的测试模块,因为它不仅是一个C API模块,而且是一个核心模块,可能是启动引导程序的一部分。)
- 奇怪的是,即使使用gc.disable()禁用自动垃圾收集,gc.collect()的输出仍然是0。
- @禁用自动垃圾收集仅禁用周期检测器的定期运行。refs仍在计算中,任何进入0 refs的内容仍将立即删除。
- 导入模块后,sys.getrefcount(x)为48。比我想象的要高!
- stackoverflow.com/a/33398553/1319284 sys.get_referers根据此答案获取对象的所有引用
- @Ninjakannon这可能是因为你在回复中这样做了,你最终会得到一些东西,比如_被临时分配给math,其他的东西从那里获取参考。或者,可能是因为每个模块全局都是一个内置方法包装器对象,模块作为其_self成员。或者这可能是因为python使用的是数学模块本身,而您通过导入它所做的只是将其从46次重复提升到48次。或者…
- @顺便说一句,库什凯姆的名字是gc.get_referrers,不是sys。
基于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一样简单。现在,当它被收集时,它的析构函数将运行,并且您将有证据证明它是被收集的。(好吧,除非析构函数恢复了它,否则证明它是被收集的,但是如果析构函数除了打印一个静态字符串之外什么都不做,那就不必担心了。)
- 谢谢,这是一个有用的答案,因为它解决了我在问题中的误解,并导致了一个解决方案。我也会在我的问题中加上我根据你的答案写下的简短例子。