在Python中卸载模块
简而言之:
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 个回答
我不太确定Python的情况,但在其他编程语言中,调用类似于gc.collect()
的命令并不会立即释放未使用的内存。只有在真的需要这些内存的时候,它才会释放。
另外,Python暂时保留这些模块在内存中是有道理的,因为如果以后需要再次加载它们,就可以更快地使用。
我在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提供一个更新,开始讨论,如果我们能取得更多进展,我会把它删掉。
Python不支持卸载模块。
不过,除非你的程序在运行过程中加载了无限数量的模块,否则这并不是你内存泄漏的原因。模块通常在程序启动时加载一次,之后就不会再加载了。所以,你的内存泄漏问题很可能出在其他地方。
如果真的出现了你的程序在运行中加载无限模块的情况,那你可能需要重新设计一下你的程序。;-)