在Python中卸载模块

时间:2010-06-23 21:46:04

标签: python memory-leaks

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开发人员联系过这个问题,事实上它在未来五年内完全“{3}}”。 (见链接)

  

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


在我最近在我的应用程序中搜索memleak期间,我已将其缩小到模块,即我无法垃圾收集卸载模块。使用下面列出的任何方法卸载模块会在内存中留下数千个对象。换句话说 - 我无法在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非垃圾对象。 我们尝试从httplib删除sys.modules

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%)......那只是来自一个模块......那里面甚至没有任何大的内容......

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

更简单的复制形式的完整资源:not going to be possible to unload a module

5 个答案:

答案 0 :(得分:16)

Python不支持卸载模块。

但是,除非您的程序随着时间的推移加载无限数量的模块,否则这不是内存泄漏的来源。模块通常在启动时加载一次,就是这样。你的记忆泄漏很可能在其他地方。

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

答案 1 :(得分:4)

我在python3(十年后)(现在为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实用程序等。这里是开始@shuttle对话的更新,如果我们可以取得更多进展,它将删除。

答案 2 :(得分:3)

我不确定Python,但在其他语言中,调用相当于gc.collect()释放未使用的内存 - 只有当内存为/时才会释放该内存实际需要。

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

答案 3 :(得分:0)

(你应该尝试写更简洁的问题;我只阅读开头并略过其余部分。)我在开始时看到一个简单的问题:

sm = sys.modules.copy()

你制作了sys.modules的副本,所以现在你的副本有一个对模块的引用 - 所以当然不会收集它。你可以用gc.get_referrers看到它的含义。

这很好用:

# 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()
我们从sys.modules中删除它后会立即卸载

,因为没有对模块的剩余引用。 (导入后执行module1 = None也可以工作 - 为了清楚起见,我只是将导入放在另一个函数中。您所要做的就是删除对它的引用。)

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

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

通常使用它有一些棘手的问题:检测哪些模块取决于您要卸载的模块;知道是否也可以卸载它们(在很大程度上取决于你的用例);检查所有这些时处理线程(看一下imp.acquire_lock),等等。

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

答案 4 :(得分:0)

Python's small object manager rarely returns memory back to the Operating System.来自herehere。因此,严格来说,即使设计对象是“ gc收集”的,python也有(通过设计)某种内存泄漏。