阻止Python缓存导入的模块

时间:2010-05-27 06:16:17

标签: python import ipython python-module python-import

在使用IPython在Python中开发一个大型项目(分成几个文件和文件夹)时,我遇到了缓存导入模块的麻烦。

问题是指令import module仅读取模块一次,即使该模块已更改!因此,每次我在包中更改某些内容时,都必须退出并重新启动IPython。痛苦。

有没有办法正确强制重装一些模块?或者,更好的是,以某种方式阻止Python缓存它们?

我尝试了几种方法,但都没有效果。特别是我遇到了非常非常奇怪的错误,比如某些模块或变量神秘地变得等于None ......

我找到的唯一明智的资源是来自pyunit的Reloading Python modules,但我没有检查过它。我想要那样的东西。

一个很好的选择是让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?

为什么我的模块在一个大文件或几个子模块中有这么不同?为什么这种方法不起作用?

8 个答案:

答案 0 :(得分:25)

import检查模块是否在sys.modules中,如果是,则返回它。如果要导入从磁盘加载模块,可以先在sys.modules中删除相应的密钥。

有一个reload内置函数,给定一个模块对象,它将从磁盘重新加载,并将放在sys.modules中。 编辑 - 实际上,它将从磁盘上的文件重新编译代码,然后在现有模块的__dict__中重新评估它。可能与制作新模块对象有很大不同。

但迈克格雷厄姆是正确的;如果您甚至有一些活动对象引用您不再需要的模块内容,那么重新加载就很难了。现有对象仍将引用它们实例化的类是一个明显的问题,但是通过from module import symbol创建的所有引用仍将指向旧版本模块中的任何对象。许多巧妙的错误都是可能的。

编辑:我同意重新启动解释器是迄今为止最可靠的事情。但出于调试目的,我想你可以尝试类似下面的内容。我确定有一些不适用的极端情况,但是如果你在你的包中加载模块时没有做任何太疯狂的事情(否则),它可能会有用。 / p>

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

答案 1 :(得分:10)

退出并重新启动解释器是最佳解决方案。任何类型的实时重新加载或不缓存策略都无法无缝地工作,因为来自不再存在的模块的对象可以存在,并且因为模块有时会存储状态,并且因为即使您的用例确实允许热重新加载它也太复杂而无法考虑值得。

答案 2 :(得分:10)

使用IPython来autoreload extension,它会在每次函数调用之前自动重复导入。它至少在简单的情况下起作用,但不要太依赖它:根据我的经验,仍然需要不时地重新启动解释器,特别是当代码更改仅在间接导入的代码上发生时。

链接页面的用法示例:

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

答案 3 :(得分:4)

这里已经有了一些非常好的答案,但是值得了解dreload,这是IPython中可用的函数,它可以作为"深度重载"。来自文档:

  

IPython.lib.deepreload模块允许您以递归方式重新加载   module:将重新加载对其任何依赖项所做的更改   无需退出。要开始使用它,请执行以下操作:

http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload

它可以作为"全球"在IPython笔记本中(至少我的版本,运行v2.0)。

HTH

答案 4 :(得分:2)

您可以使用PEP 302中描述的导入钩子机制来加载不是自己的模块,但是某种代理对象允许您使用底层模块对象执行任何操作 - 重新加载它,删除对它的引用等。

其他好处是您当前现有的代码不需要更改,并且可以从代码中的单个点剥离此附加模块功能 - 您实际上将查找器添加到sys.meta_path

关于实现的一些想法:创建将同意找到任何模块的finder,除了builtin(你与内置模块无关),然后创建将从types.ModuleType而不是真实的子代表返回代理对象的加载器模块对象。请注意,不强制加载器对象创建对已加载模块的显式引用sys.modules,但强烈建议它,因为正如您已经看到的那样,它可能会无法正常地失败。代理对象应该捕获并转发所有__getattr____setattr____delattr__到它所引用的基础实模块。您可能不需要定义__getattribute__,因为您不会使用代理方法隐藏实际模块内容。所以,现在你应该以某种方式与代理进行通信 - 你可以创建一些特殊方法来删除底层引用,然后导入模块,从返回的代理中提取引用,删除代理并保持对重新加载模块的引用。 Phew看起来很可怕,但是每次都不用重新加载Python就可以修复你的问题。

答案 5 :(得分:2)

我在我的项目中使用PythonNet。幸运的是,我发现有一个命令可以完美地解决这个问题。

using (Py.GIL())
        {
            dynamic mod = Py.Import(this.moduleName);
            if (mod == null)
                throw new Exception( string.Format("Cannot find module {0}. Python script may not be complied successfully or module name is illegal.", this.moduleName));

            // This command works perfect for me!
            PythonEngine.ReloadModule(mod);

            dynamic instance = mod.ClassName();

答案 6 :(得分:0)

Think twice for quitting and restarting in production

无需退出并重新启动的简单解决方案是使用从imp重新加载

import moduleA, moduleB
from imp import reload
reload (moduleB)

答案 7 :(得分:0)

对于Python 3.4及更高版本

import importlib 
importlib.reload(<package_name>) 
from <package_name> import <method_name>

有关详细信息,请参见documentation以下。