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