在Python中卸载模块

77 投票
5 回答
26496 浏览
提问于 2025-04-16 00:21

简而言之:

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

现在我们有7000个非垃圾对象。 让我们尝试从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

这……如果我们在exec里面import一个模块呢?

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%)……这仅仅是一个模块……里面甚至没有什么大的东西……

好吧,认真说,我的错误在哪里? 我该如何卸载一个模块并清空它的所有内容? 或者说Python的模块就是一个巨大的内存泄漏?

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

5 个回答

6

我不太确定Python的情况,但在其他编程语言中,调用类似于gc.collect()的命令并不会立即释放未使用的内存。只有在真的需要这些内存的时候,它才会释放。

另外,Python暂时保留这些模块在内存中是有道理的,因为如果以后需要再次加载它们,就可以更快地使用。

11

我在Python 3(现在是python3.8,已经过了10年)上找不到一个权威的观点。不过,现在我们在百分比上可以做得更好。

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工具等等。我主要把这个放在这里,是为了给@shuttle提供一个更新,开始讨论,如果我们能取得更多进展,我会把它删掉。

28

Python不支持卸载模块。

不过,除非你的程序在运行过程中加载了无限数量的模块,否则这并不是你内存泄漏的原因。模块通常在程序启动时加载一次,之后就不会再加载了。所以,你的内存泄漏问题很可能出在其他地方。

如果真的出现了你的程序在运行中加载无限模块的情况,那你可能需要重新设计一下你的程序。;-)

撰写回答