防止 Python 缓存导入的模块
Posted
技术标签:
【中文标题】防止 Python 缓存导入的模块【英文标题】:Prevent Python from caching the imported modules 【发布时间】:2011-02-24 12:33:08 【问题描述】:在使用 IPython 在 Python 中开发大型项目(拆分为多个文件和文件夹)时,我遇到了缓存导入模块的问题。
问题是指令import module
只读取该模块一次,即使该模块已更改!所以每次我更改包中的某些内容时,我都必须退出并重新启动 IPython。很痛苦。
有没有办法正确强制重新加载某些模块?或者,更好的是,以某种方式阻止 Python 缓存它们?
我尝试了几种方法,但都没有奏效。特别是我遇到了非常非常奇怪的错误,例如某些模块或变量神秘地等于None
...
我找到的唯一明智的资源是Reloading Python modules,来自pyunit,但我没有检查它。我想要这样的东西。
一个不错的选择是让 IPython 重新启动,或者以某种方式重新启动 Python 解释器。
那么,如果你用 Python 开发,你找到了解决这个问题的方法吗?
编辑
为了清楚起见:显然,我知道一些旧变量取决于模块的先前状态可能会保留。那个我能接受。为什么在 Python 中强制重新加载模块而不发生各种奇怪的错误如此困难?
更具体地说,如果我将整个模块放在 one 文件 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 来说如此不同?为什么这种方法行不通??
【问题讨论】:
这实际上是一个非常流行的问题;每个月都会弹出。正如 Mike 指出的那样,当前的共识是重新启动您的解释器。 首先,我不明白你为什么要禁止 AttributeError。del sys.modules[mod]
根本不可能引发 AttributeError... 除非您将 sys
反弹到内置 sys 模块以外的其他东西。难道仅仅是在第二个代码中,你有sys.module
(它将用正常的sys引发AttributeError),而在第一个代码中,你有sys.modules
? :-P
【参考方案1】:
import
检查模块是否在sys.modules
中,如果是,则返回它。如果要导入以从磁盘重新加载模块,可以先删除sys.modules
中的相应键。
有一个reload
内置函数,它会在给定一个模块对象的情况下从磁盘重新加载它,并将其放置在sys.modules
中。 编辑 -- 实际上,它会从磁盘上的文件中重新编译代码,然后在现有模块的__dict__
中重新评估它。可能与创建新模块对象非常不同。
不过,迈克·格雷厄姆是对的;如果您甚至有一些活动对象引用了您不再需要的模块的内容,那么要正确地重新加载是很困难的。现有对象仍将引用它们被实例化的类是一个明显的问题,但通过from module import symbol
创建的所有引用仍将指向旧版本模块中的任何对象。许多微妙的错误都是可能的。
编辑:我同意重启解释器是迄今为止最可靠的事情的共识。但是出于调试目的,我想您可以尝试以下方法。我确信在某些极端情况下这是行不通的,但是如果您在 your 包中加载模块时没有做任何太疯狂的事情(否则),它可能会很有用。
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
【讨论】:
我尝试了这些方法,既删除了sys.modules
中的相应条目,也使用了reload
。它偶尔会起作用,但有时会产生非常非常微妙和奇怪的错误,其中一些变量突然变成None
,完全没有原因。
“医生,我这样走很痛”,“好吧,不要那样做”。我一直将reload
视为调试时的便利,并认为它是一个文档错误,考虑到它提供的语义错误有多严重,它是为实际实时加载而规定的。
我发现您的答案中的del sys.modules[key]
行对我的场景很有用。我同意这不会像问题那样阻止缓存,但是这些功能可以帮助人们建立一种行为,例如定期使 python 缓存过期。【参考方案2】:
退出并重新启动解释器是最好的解决方案。任何类型的实时重载或无缓存策略都不会无缝工作,因为来自不再存在的模块的对象可能存在,而且模块有时会存储状态,而且即使您的用例确实允许热重载,但考虑起来太复杂了值得。
【讨论】:
迷信!即使在in PyUnit 这样的一般情况下,热重载也可以相对优雅地实现,但在特定情况下,设置它并不需要很长时间并且回报非常好。例如,在我的方案中,有一个轻量级主模块,其中包含一个单例,我的所有其他实例都挂起,并且只调用它们的方法,而不是函数。重新加载是通过for each i: del sys.modules[mymodule_i]
、reload(..)
-ing 它们,然后将每个实例的 __class__
从旧到新交换。
可调试、可理解的代码简单明了。以这种方式增加复杂性是不明智或不合理的。我不建议在远程做任何像以前那样巧妙的事情。
除非目标环境不能如此容易地重新启动。例如,Blender 需要重新启动应用程序以清除解释器状态,如果加载项大于一个模块,这对于打开和关闭(加载项 UI 允许)的加载项来说是可怕的用户体验。
【参考方案3】:
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
【讨论】:
我发现虽然%autoreload 2
在层次结构很深的情况下不起作用,但这个解决方案总是有效的:***.com/a/13096672/311567 我希望它会成为 ipython 中的默认值【参考方案4】:
适用于 Python 3.4 及以上版本
import importlib
importlib.reload(<package_name>)
from <package_name> import <method_name>
详情请参阅下方documentation。
【讨论】:
重新加载只需要父模块。您需要递归地重新加载所有这些,包括 module.version。更多细节:pprint(sys.modules)【参考方案5】:这里已经有一些非常好的答案,但值得了解的是 dreload,它是 IPython 中可用的一个函数,用作“深度重新加载”。来自文档:
IPython.lib.deepreload 模块允许你递归地重新加载一个 模块:对其任何依赖项所做的更改将被重新加载 无需退出。要开始使用它,请执行以下操作:
http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload
它在 IPython 笔记本中作为“全局”提供(至少是我的版本,它正在运行 v2.0)。
HTH
【讨论】:
【参考方案6】:您可以使用PEP 302 中描述的导入钩子机制来加载不是模块本身,而是某种代理对象,它允许您对底层模块对象执行任何您想做的事情——重新加载它、删除对它的引用等。
额外的好处是您当前现有的代码不需要更改,并且可以从代码中的单个点撕下此附加模块功能 - 您实际上将 finder 添加到 sys.meta_path
。
关于实现的一些想法:创建将同意查找除内置模块之外的任何模块的查找器(您与内置模块无关),然后创建将返回从types.ModuleType
子类化的代理对象而不是真正的模块对象的加载器.请注意,加载器对象不会强制创建对已加载模块的显式引用到sys.modules
,但强烈建议这样做,因为正如您已经看到的,它可能会意外失败。代理对象应该捕获并转发所有__getattr__
、__setattr__
和__delattr__
到它保持引用的底层真实模块。您可能不需要定义__getattribute__
,因为您不会使用代理方法隐藏真正的模块内容。所以,现在你应该以某种方式与代理通信——你可以创建一些特殊的方法来删除底层引用,然后导入模块,从返回的代理中提取引用,删除代理并保持对重新加载模块的引用。呼,看起来很吓人,但应该可以解决您的问题,而无需每次都重新加载 Python。
【讨论】:
【参考方案7】:我在我的项目中使用 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();
【讨论】:
我相信问题是关于重新启动 python 解释器。您似乎在其中使用 C# 和 python 引擎。 ?可能不是 OP 正在寻找的东西。 @user7175781 无论我尝试什么,语法都无法在我的系统上运行。我猜 PythonNet 的语法和 API 与 ipython 不同。我希望您的示例能够正常工作,但在解释器报告了许多错误之后,我终于意识到您说的是 PythonNet 语言。【参考方案8】:Think twice for quitting and restarting in production
无需退出和重新启动的简单解决方案是使用 imp 的重新加载
import moduleA, moduleB
from imp import reload
reload (moduleB)
【讨论】:
imp
已弃用以上是关于防止 Python 缓存导入的模块的主要内容,如果未能解决你的问题,请参考以下文章