70

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

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

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

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

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

一个不错的选择是让 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 会有如此不同?为什么这种方法不起作用?

4

8 回答 8

29

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

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

迈克格雷厄姆是对的。如果您甚至有一些活动对象引用了您不再需要的模块内容,那么要正确地重新加载是很困难的。现有对象仍将引用它们被实例化的类是一个明显的问题,但通过 所创建的所有引用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
于 2010-05-27T06:28:50.307 回答
18

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

于 2010-05-27T06:20:29.513 回答
14

IPython 附带了autoreload 扩展,它在每个函数调用之前自动重复导入。它至少在简单的情况下有效,但不要过分依赖它:根据我的经验,仍然需要不时重启解释器,特别是当代码更改仅发生在间接导入的代码上时。

链接页面中的使用示例:

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
于 2013-07-09T14:46:28.193 回答
10

对于 Python 3.4 及以上版本

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

有关详细信息,请参阅以下文档

于 2019-03-26T07:33:32.147 回答
5

这里已经有一些非常好的答案,但值得了解 dreload,它是 IPython 中可用的函数,用作“深度重新加载”。从文档中:

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

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

它在 IPython 笔记本中作为“全局”提供(至少我的版本,它正在运行 v2.0)。

高温高压

于 2014-04-23T09:18:05.783 回答
3

您可以使用PEP 302中描述的导入钩子机制来加载不是模块本身,而是某种代理对象,它允许您对底层模块对象执行任何您想做的事情——重新加载它,删除对它的引用等。

额外的好处是您当前现有的代码不需要更改,并且这个额外的模块功能可以从代码中的一个点中删除 - 您实际上将 finder 添加到sys.meta_path.

关于实现的一些想法:创建将同意查找除内置模块之外的任何模块的查找器(您与内置模块无关),然后创建将返回代理对象的加载器,该代理对象是从types.ModuleType真正的模块对象继承的子类。请注意,加载器对象不会强制创建对已加载模块sys.modules的显式引用,但强烈建议这样做,因为正如您已经看到的那样,它可能会意外失败。代理对象应该捕获并转发 all __getattr____setattr__并转发__delattr__到它所引用的底层真实模块。您可能不需要定义__getattribute__因为你不会用你的代理方法隐藏真正的模块内容。所以,现在你应该以某种方式与代理通信——你可以创建一些特殊的方法来删除底层引用,然后导入模块,从返回的代理中提取引用,删除代理并保持对重新加载的模块的引用。呼,看起来很吓人,但应该可以解决您的问题,而无需每次都重新加载 Python。

于 2010-10-12T02:29:57.497 回答
3

我在我的项目中使用 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();
于 2016-11-18T00:53:54.820 回答
3

三思而后行退出和重新启动生产

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

import moduleA, moduleB
from imp import reload
reload (moduleB)
于 2019-02-01T19:36:47.997 回答