卸载 Python 中的模块

TL/DR:

import gc, sys


print len(gc.get_objects()) # 4073 objects in memory


# Attempt to unload the module


import httplib
del sys.modules["httplib"]
httplib = None


gc.collect()
print len(gc.get_objects()) # 6745 objects in memory

更新 关于这个问题,我已经联系了 Python 开发人员,实际上完全是“未来五年”的 无法卸载模块

请接受 Python 确实不支持在2.x 中为严重的、基本的、不可克服的技术问题卸载模块。


在我最近寻找我的应用程序中的一个内存泄漏的过程中,我把范围缩小到了模块,也就是说我无法使用 收垃圾一个卸载的模块。使用下面列出的 任何方法卸载一个模块会在内存中留下数千个对象。换句话说,我不能卸载 Python 中的模块..。

问题的其余部分是尝试以某种方式垃圾收集模块。

让我们试试:

import gc
import sys


sm = sys.modules.copy()  # httplib, which we'll try to unload isn't yet
# in sys.modules, so, this isn't the source of problem


print len(gc.get_objects()) # 4074 objects in memory

让我们保存一个 sys.modules副本,以便稍后尝试恢复它。 这是一个基线4074对象,我们应该回到这里。

让我们导入一个模块:

import httplib
print len(gc.get_objects()) # 7063 objects in memory

我们已经找到了7K 非垃圾对象。 让我们试着从 sys.modules中移除 httplib

sys.modules.pop('httplib')
gc.collect()
print len(gc.get_objects()) # 7063 objects in memory

好吧,那没有用。嗯,但是在 __main__中没有提到吗? 哦,是的:

del httplib
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

万岁,下降了300个物体。不过,没有雪茄,这是远远超过4000个原始物体。 让我们尝试从拷贝中恢复 sys.modules

sys.modules = sm
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

嗯,这是毫无意义的,没有改变. 。 也许如果我们消灭全球..。

globals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

当地人?

locals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

什么. . 如果我们 imported一个模块内的 exec

local_dict = {}
exec 'import httplib' in local_dict
del local_dict
gc.collect()
print len(gc.get_objects())  # back to 7063 objects in memory

现在,这不公平,它进口到 __main__,为什么?它不应该离开 local_dict... 啊!我们回到完全进口的 httplib。 也许我们可以用一个假的东西代替它?

from types import ModuleType
import sys
print len(gc.get_objects())  # 7064 objects in memory

该死的... ! !

sys.modules['httplib'] = ModuleType('httplib')
print len(gc.get_objects())  # 7066 objects in memory

去死吧,去死吧!

import httplib
for attr in dir(httplib):
setattr(httplib, attr, None)
gc.collect()
print len(gc.get_objects())  # 6749 objects in memory

好吧,在所有的尝试之后,最好的是 + 2675(接近 + 50%)从起点... 这只是从一个模块... 这甚至没有任何大的内部..。

好了,说真的,我的错误在哪里? 我如何卸载一个模块并清除它的所有内容? 还是巨蟒的模块是一个巨大的内存泄漏?

完整源代码以更简单的形式复制: http://gist.github.com/450606

24387 次浏览

Python 不支持卸载模块。

但是,除非您的程序随着时间的推移加载无限数量的模块,否则这不是内存泄漏的根源。模块通常在启动时加载一次,仅此而已。您的内存泄漏很可能发生在其他地方。

在不太可能的情况下,您的程序确实随着时间的推移加载无限数量的模块,您可能应该重新设计您的程序。;-)

(你应该试着写得更简洁些,我只看了开头,其余的略读了一下。)我在一开始就看到了一个简单的问题:

sm = sys.modules.copy()

您创建了一个 sys.module 的副本,所以现在您的副本有一个对模块的引用——所以当然不会收集它。您可以通过 gc.get _ reference 查看引用它的内容。

这种方法很有效:

# module1.py
class test(object):
def __del__(self):
print "unloaded module1"
a = test()


print "loaded module1"

.

# testing.py
def run():
print "importing module1"
import module1
print "finished importing module1"


def main():
run()
import sys
del sys.modules["module1"]
print "finished"


if __name__ == '__main__':
main()

Module1在从 sys.module 中删除后立即被卸载,因为没有剩余的对模块的引用。(导入之后执行 module1 = None也可以——为了清楚起见,我只是将导入放在另一个函数中。你所要做的就是丢掉你对它的引用。)

现在,这在实践中有点棘手,因为有两个问题:

  • 为了收集模块,对模块的所有引用必须是不可访问的(与收集任何对象一样)。这意味着导入它的任何其他模块也需要解引用和重新加载。
  • 如果在其他地方仍然引用某个模块时从 sys.module 中删除该模块,那么您就创建了一种不寻常的情况: 该模块仍然被代码加载和使用,但是模块加载器不再知道它。下次导入模块时,不会获得对现有模块的引用(因为您删除了该模块的记录) ,所以它将加载模块的第二个共存副本。这会导致严重的一致性问题。因此,在最终将模块从 sys.module 中删除之前,请确保没有对该模块的剩余引用。

通常使用这种方法会遇到一些棘手的问题: 检测哪些模块依赖于您正在卸载的模块; 知道是否也可以卸载这些模块(这在很大程度上取决于您的用例) ; 在检查所有这些内容时处理线程(参见 imp.access _ lock) ,等等。

我可以设计一个案例,这样做可能是有用的,但大多数时候,我建议只是重新启动应用程序,如果它的代码发生变化。你可能只会让自己头疼。

我不确定是否使用 Python,但在其他语言中,调用相当于 gc.collect()没有会释放未使用的内存——它只会在确实需要内存时释放该内存。

否则,Python 将模块暂时保留在内存中是有意义的,以防需要再次加载它们。

给你给你。因此,严格地说,python (根据设计)具有一种内存泄漏,即使在对象被“ gc 收集”时也是如此。

在 python3(10年后)(现在是 python3.8)中,我找不到这方面的权威观点。然而,我们现在可以做得更好。

import gc
import sys


the_objs = gc.get_objects()
print(len(gc.get_objects())) # 5754 objects in memory
origin_modules = set(sys.modules.keys())
import http.client # it was renamed ;)


print(len(gc.get_objects())) # 9564 objects in memory
for new_mod in set(sys.modules.keys()) - origin_modules:
del sys.modules[new_mod]
try:
del globals()[new_mod]
except KeyError:
pass
try:
del locals()[new_mod]
except KeyError:
pass
del origin_modules
# importlib.invalidate_caches()  happens to not do anything
gc.collect()
print(len(gc.get_objects())) # 6528 objects in memory

只增长了13% 。如果您查看在新的 gc.get_objects中加载的对象类型,其中一些是内置函数、源代码、 random.*实用程序、 datetime实用程序等。我主要是留下这里作为一个更新开始@穿梭对话,并将删除,如果我们可以取得更多的进展。