防止Python缓存导入的模块

79 投票
8 回答
74236 浏览
提问于 2025-04-15 23:13

在用IPython开发一个比较大的Python项目时,我遇到了一个关于缓存导入模块的问题。

问题是,使用import module这条指令时,Python只会读取一次这个模块,即使这个模块已经改变了!所以每次我在我的包里做了修改后,都得退出再重启IPython,这真是太麻烦了。

有没有什么办法可以强制重新加载某些模块?或者更好的是,能不能让Python不缓存这些模块?

我尝试了几种方法,但都不奏效。特别是我遇到了一些非常奇怪的错误,比如某些模块或变量神秘地变成了None……

我找到的唯一有用的资源是来自pyunit的重新加载Python模块,但我还没仔细看过。我希望能找到类似的东西。

一个不错的替代方案是让IPython重启,或者以某种方式重启Python解释器。

所以,如果你在用Python开发,你找到什么解决这个问题的方法了吗?

编辑

为了让事情更清楚:显然,我明白一些旧变量可能会依赖于模块之前的状态而保留。这我可以接受。但为什么在Python中强制重新加载一个模块这么困难,还会出现各种奇怪的错误呢?

更具体地说,如果我把整个模块放在一个文件module.py里,那么下面的代码就能很好地工作:

import sys
try:
    del sys.modules['module']
except AttributeError:
    pass
import module

obj = module.my_class()

这段代码运行得非常顺利,我可以在不退出IPython的情况下开发好几个月。

然而,每当我的模块由几个子模块组成时,情况就变得复杂了:

import os
for mod in ['module.submod1', 'module.submod2']:
    try:
        del sys.module[mod]
    except AttributeError:
        pass
# sometimes this works, sometimes not. WHY?

为什么在Python中,把模块放在一个大文件里和放在几个子模块里会有这么大的区别?为什么这种方法就不行呢?

8 个回答

14

IPython里有一个叫做自动重载扩展的功能,它可以在每次调用函数之前自动重新导入模块。这个功能在简单的情况下效果不错,但不要过于依赖它:根据我的经验,有时候还是需要重启解释器,特别是当你修改的代码是间接导入的代码时。

下面是链接页面中的使用示例:

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from foo import some_function

In [4]: some_function()
Out[4]: 42

In [5]: # open foo.py in an editor and change some_function to return 43

In [6]: some_function()
Out[6]: 43
34

import 会检查模块是否已经在 sys.modules 里,如果找到了,就直接返回这个模块。如果你想让 import 从磁盘重新加载这个模块,你需要先把 sys.modules 中对应的键删除。

还有一个叫 reload 的内置函数,它可以根据模块对象,从磁盘重新加载这个模块,然后把它放到 sys.modules 里。补充说明 -- 实际上,它会从磁盘上的文件重新编译代码,然后在现有模块的 __dict__ 中重新评估。这和创建一个新的模块对象是有很大不同的。

不过,Mike Graham 说得对;如果你有一些活跃的对象仍然引用着你不想要的模块内容,正确地重新加载是很困难的。现有对象仍然会引用它们被实例化时的类,这显然是个问题,而且通过 from module import symbol 创建的所有引用仍然会指向旧版本模块中的对象。这可能会导致很多微妙的错误。

补充说明:我同意大家的看法,重启解释器是最可靠的做法。但为了调试的目的,我想你可以尝试以下方法。我确信有一些特殊情况是这个方法不适用的,但如果你在 你的 包中没有做太疯狂的模块加载,这可能会有用。

def reload_package(root_module):
    package_name = root_module.__name__

    # get a reference to each loaded module
    loaded_package_modules = dict([
        (key, value) for key, value in sys.modules.items() 
        if key.startswith(package_name) and isinstance(value, types.ModuleType)])

    # delete references to these loaded modules from sys.modules
    for key in loaded_package_modules:
        del sys.modules[key]

    # load each of the modules again; 
    # make old modules share state with new modules
    for key in loaded_package_modules:
        print 'loading %s' % key
        newmodule = __import__(key)
        oldmodule = loaded_package_modules[key]
        oldmodule.__dict__.clear()
        oldmodule.__dict__.update(newmodule.__dict__)

我很简单地测试了一下,像这样:

import email, email.mime, email.mime.application
reload_package(email)

打印:

reloading email.iterators
reloading email.mime
reloading email.quoprimime
reloading email.encoders
reloading email.errors
reloading email
reloading email.charset
reloading email.mime.application
reloading email._parseaddr
reloading email.utils
reloading email.mime.base
reloading email.message
reloading email.mime.nonmultipart
reloading email.base64mime
21

最好的解决办法就是退出并重新启动解释器。任何实时更新或者不缓存的策略都不会顺利进行,因为已经不存在的模块中的对象可能还会存在。而且模块有时会保存状态。即使你的情况确实允许热更新,这样做也太复杂了,不值得去考虑。

撰写回答