2010-06-23 81 views
49

TL/DR:卸载模块在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 

UPDATE 我已经联系Python开发者对这个问题,事实上它是 “在未来五年” not going to be possible to unload a module完全。 (请参阅链接)

请接受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非垃圾对象。 让我们尝试从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 

Hooray,下降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 

什么..如果我们importedexec内的模块?

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 

Die modules,die !!

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

回答

17

Python不支持卸载模块。

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

如果程序确实会随着时间的推移加载无限数量的模块,那么您应该重新设计程序。 ;-)

+1

是的,它确实加载了合理无限数量的模块 - 它是一个Web应用服务器,接受它自己的源代码的新版本并重新加载它(这是非常标准的Web任务)。漏洞源于旧代码仍然存在于内存中,即使被替换,即使无法访问... – 2010-06-23 21:52:49

+0

Python确实支持卸载模块。它们是垃圾收集的,就像Python中的其他对象一样。 – 2010-06-23 22:56:24

+1

@Slava:你可能想看看'mod_python'的源代码,它有自己的导入器,用于处理重装模块而不产生内存泄漏。那里可能有一些你可以使用的代码。 – 2010-06-23 23:00:33

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就会被卸载,因为没有剩余的模块引用。 (导入后做会工作,太 - 我只是把导入另一个函数为清楚所有你需要做的就是放下你对它的引用。)

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

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

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

我可以设法做一个这样做可能有用的情况,但大多数情况下,我建议只是在代码更改时重新启动应用程序。你可能会让自己头疼。

+8

嗯,不是snyde,但你应该阅读这个问题,或者至少在标题(或至少是标签)中“完全”这个词。问题不是我不想重新加载,问题是*内存泄漏*与任何(列出的)模块删除(包括您提出的那些*,*在我的问题中列出*)以及其他十几个)。 其实我在很晚的阶段添加了'sys.modules.copy()',删除它不会改变任何东西(尝试自己)。 – 2010-06-23 23:08:47

+1

来源,尝试:http://gist.github.com/450606。尝试删除sys.modules.copy,即使删除了所有对模块的引用,您仍会看到对象增加了50%以上。 – 2010-06-23 23:19:58

+0

请参阅此处以了解缺点(使用您的代码):http://gist.github.com/450726。我不尝试加载 - 卸载'sys',因为我们使用'sys.modules',所以我使用'httplib' - 你可以尝试任何其他的。 – 2010-06-23 23:29:18

3

我不知道Python,但在其他语言中,调用的gc.collect()相当于不释放未使用的内存 - 如果/当实际所需的内存也只会释放内存。

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

+0

问题是我需要用新版本替换它们。即使我用相同大小的模块1对1替换它 - 内存使用增长(泄漏)...感谢您的建议,但。 – 2010-06-23 23:44:47