在 Python 中卸载模块

Posted

技术标签:

【中文标题】在 Python 中卸载模块【英文标题】:Unload a module in Python 【发布时间】:2011-03-07 13:19:53 【问题描述】:

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 开发人员,确实是 not going to be possible to unload a module 完全“在未来五年内”。 (见链接)

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


在我最近在我的应用程序中寻找 memleak 期间,我已将其范围缩小到模块,即我无法垃圾收集卸载的模块。使用下面列出的 any 方法来卸载模块会在内存中留下数千个对象。换句话说 - 我无法......

剩下的问题是尝试以某种方式对模块进行垃圾收集。

让我们试试吧:

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%)...这只是来自一个模块...内部甚至没有任何大的东西...

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

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

【问题讨论】:

1.你有没有一一检查过,剩下的对象到底是什么? 2.ou 正在尝试使用“python”模块进行测试。您是否尝试过使用您创建的幼稚大模块的相同方法?一个不引用自身外部任何东西的模块? 【参考方案1】:

Python 不支持卸载模块。

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

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

【讨论】:

是的,它确实加载了合理无限数量的模块——它是一个网络应用服务器,可以接受它自己的源代码的新修订版并重新加载它(这是非常标准的网络任务)。泄漏是因为旧代码仍然存在于内存中,即使被替换,即使无法访问...... Python 确实支持卸载模块。它们像 Python 中的所有其他对象一样被垃圾收集。 @Slava:您可能想看看mod_python 的源代码,它有自己的导入器,旨在处理重新加载模块而不会产生内存泄漏。里面可能有一些你可以使用的代码。 @Glenn:它们是可收集垃圾的对象,是的。真与假也是如此。你能把引用计数降到0吗?没那么容易。另见:bit.ly/9mvndb @Slava (3 cmets up):部分,但不是全部。其中也有很多 Python 代码,包括导入器。见the source。【参考方案2】:

我不确定 Python,但在其他语言中,调用 gc.collect() 的等价物不会释放未使用的内存 - 它只会在实际需要内存时释放该内存.

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

【讨论】:

问题是我需要用新版本替换它们。即使我用相同大小的模块一对一地替换它 - 内存使用量也会增加(泄漏)......不过,谢谢你的建议。【参考方案3】:

Python's small object manager rarely returns memory back to the Operating System. 来自here 和here。所以,严格来说,python 有(通过设计)一种内存泄漏,即使对象被“gc 收集”。

【讨论】:

【参考方案4】:

我在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 实用程序等。我主要将这里作为更新开始@shuttle 的对话,如果我们能取得更多进展,将删除。

【讨论】:

感谢这里的努力!看到百分比变化非常有趣【参考方案5】:

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

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()

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

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

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

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

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

【讨论】:

好吧,不要装傻,但您应该已经阅读了这个问题,或者至少阅读了标题中的“完全”一词(或至少阅读了标签)。问题不是我不想重新加载,问题是 内存泄漏 与任何(列出的)类型的模块删除相关(包括您提出的那些, 在我的问题中列出,以及其他十几个问题)。实际上我在很晚的阶段添加了sys.modules.copy(),删除它不会改变任何东西(你自己试试)。 来源,尝试:gist.github.com/450606。尝试删除 sys.modules.copy,您会发现即使删除了对模块的所有引用,对象仍然增加了 50% 以上。 看看这里有什么问题(使用你的代码):gist.github.com/450726。我不尝试加载-卸载sys,因为我们在sys.modules 上运行,所以我使用httplib - 你可以尝试其他任何方式。 澄清一下:httplib,我尝试卸载它 - 尚未在 sys.modules 中,当我创建副本时。 +1 表示“你可能只会让自己头疼。”

以上是关于在 Python 中卸载模块的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 3.x 中卸载模块(与重新加载不同)[重复]

怎么卸载已经安装的python

如何卸载我使用 easy_install 安装的 Python 模块(“egg”)?

22.Python安装和卸载第三方模块方法

在模块加载/卸载时在外部 C++ 模块中构造/销毁对象

python各种库框架的安装和卸载