阻止 Python 缓存导入的模块

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

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

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

我尝试了几种方法,但都不管用。特别是我遇到了非常非常奇怪的 bug,比如一些模块或变量神秘地等于 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 来说,无论我将模块放在一个大文件中还是放在多个子模块中,为什么会有如此大的不同呢?为什么这种方法不起作用? ?

61275 次浏览

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

import检查模块是否在 sys.modules中,如果在,则返回它。如果希望导入从磁盘新鲜加载模块,可以先在 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

您可以使用 PEP 302中描述的 import hook 机制来加载不是模块本身,而是某种代理对象,这种代理对象允许您对底层模块对象进行任何操作ーー重新加载它、删除对它的引用等等。

额外的好处是,您当前存在的代码不需要更改,而且这个额外的模块功能可以从代码中的一个点(您实际将 finder 添加到 sys.meta_path中的位置)分离出来。

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

通过 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

这里已经有一些非常好的答案,但是有必要了解 dreload,这是 IPython 中可用的一个函数,它的作用是“深度重载”。根据文件:

Deepreload 模块允许递归地重新加载 Module: 对其任何依赖项所做的更改将重新加载 要开始使用它,请:

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

它在 IPython 笔记本中以“全局”的形式提供(至少是我的版本,它运行的是 v2.0)。

高温

幸运的是,我发现有一个命令可以完美地解决这个问题。

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();

对于 Python 3.4及以上版本

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

详情请参阅下面的 文件