在命名空间中导入Python模块后,可以可靠地取消导入吗?

4 投票
2 回答
3558 浏览
提问于 2025-04-16 18:39

基本上,我有一个需要长时间运行的程序,我希望能够卸载模块并通过垃圾回收(gc)来释放内存。我读过关于删除模块的内容,发现似乎还有一些悬挂的引用会阻碍垃圾回收。

但是,如果我只在一个命名空间内导入和使用这个模块呢?换句话说,就像这样:

ns = {}
exec somecode in ns

然后我会在这个命名空间内清理 sys.modules,最后删除这个命名空间本身。

这样做会释放内存以供 CPython 重新使用吗?

如果不行,那有没有可能通过 ctypes 访问 Python 的 C API 的某些部分来实现这个目标呢?

最终结果的重要部分是,内存能够被释放,这样一个运行几周或几个月的程序,就可以可靠地卸载一个模块而不需要重新加载它。当然,在这段时间内,任何给定的模块都有可能被加载和卸载多次。我假设一个模块在加载时可能会创建大量对象,而正常的清理(sys.modules 和 del)可能会让这些对象永远留在内存中。

Jochen:是的,我可以通过多种方式解决这个问题,但我对探索 Python 的极限很感兴趣。

2 个回答

3

如果你想避免内存泄漏,最好的办法就是正常地导入模块一次,使用sys.modules的正常状态。无论后面导入这个模块多少次,它都不会占用更多的内存,因为导入机制会一直返回同一个模块。

如果因为某些原因,这种方法不适合你,比如模块是动态创建的并且只需要用一次,exec 肯定不是解决办法。你应该考虑使用其他的执行方式,可能是创建新的进程。

3

要取消导入一个模块,你需要确保已经删除了所有对这个模块的引用。这意味着你必须从所有导入了这个模块的地方删除引用,从 sys.modules 中删除引用,删除对模块中定义的任何函数或类的引用,以及删除所有对这个模块中定义的类的实例对象的引用。

在几乎所有情况下,这样做的工作量都超过了它所能节省的那点内存。如果你真的想尝试这样做,gc.get_referrers() 可能会有帮助,因为你可以删除所有已知的对这个模块的引用,只保留一个,然后追踪找到其他仍然引用它的地方。

撰写回答