重新加载已导入另一个模块的模块

Posted

技术标签:

【中文标题】重新加载已导入另一个模块的模块【英文标题】:reloading module which has been imported to another module 【发布时间】:2012-12-10 12:36:13 【问题描述】:

让我们面对现实吧,更改后重新加载 python 代码的整个业务是一团糟。不久前我发现在解释器上调用import <module>from <module> import <class/function> 更好,因为这样我就可以调用reload(module) 来获取更新的代码。

但我现在有更复杂的问题。所以我有这个文件,module1.py,在顶部它说:

from module2 import <class1>, <function1>, etc.

然后我去更改 module2 中的代码。结果表明调用reload(module1) 不会重新加载在module2 中更改的代码,即使来自module2 的代码是在module1 的顶部导入的。有没有办法在不重新启动解释器的情况下重新加载所有内容?

在任何人谈论我的风格之前,我只想说:

    我只从解释器调用reload,从不在活动代码中调用。这个问题与我在测试新代码时有关。 我从不给&lt;module&gt; import *打电话,我知道这会破坏可读性

【问题讨论】:

这是一个可以使用的递归重新加载函数:***.com/a/17194836/1020625 等等,这是一个非常好的答案。如果您将其发布为答案,那么我会接受。 对于from module import stuff 导入,这不会满足您的要求。请参阅我对答案的评论。 【参考方案1】:

要重新加载模块,您必须使用reload,并且您必须在要重新加载的模块上使用它。重新加载模块不会递归地重新加载该模块导入的所有模块。它只是重新加载那个模块。

导入模块时,会存储对它的引用,以后对该模块的导入会重新使用已导入、已存储的版本。当您重新加载 module1 时,它会重新运行 from module2 import ... 语句,但这只是重用已导入的 module2 版本,而不重新加载它。

解决此问题的唯一方法是更改​​您的代码,使其使用import module2 而不是(或补充)from module2 import ...。除非模块本身已被导入并绑定到名称(即,使用 import module 语句,而不仅仅是 from module import stuff 语句),否则您无法重新加载模块。

请注意,您可以使用两种形式的导入,重新加载导入的模块会影响后续的from 导入。也就是说,您可以这样做:

>>> import module
>>> from module import x
>>> x
2
# Change module code here, changing x to 3
>>> reload(module)
>>> from module import x
>>> x
3

这对于交互式工作来说很方便,因为它允许您使用简短的、不带前缀的名称来引用您需要的内容,同时仍然能够重新加载模块。

【讨论】:

为了完整起见,这里是重新加载的参考:docs.python.org/2/library/functions.html#reload【参考方案2】:

不要忘记导入实际上只是在命名空间中分配一个名称。因此,您可以在重新加载后重新分配该名称:

>>> reload(module2)
>>> module1.class1 = module2.class1

现在 module1 中的 class1 对象指的是从 module2 重新加载的版本。

【讨论】:

【参考方案3】:

与其在重新加载模块方面做得更好,不如在重新启动解释器方面做得更好。例如,您可以将设置代码放入自己的文件中,然后像这样运行它:

$ python -i setup.py
>>>

这将运行 setup.py,然后让您进入交互式提示。或者,与其在交互式提示中做大量工作,不如编写自动化测试来为您完成工作。

你是对的,在 Python 中重新加载模块是一团糟。该语言的语义使得在进程运行时更改代码变得困难。学会不需要重新加载模块,你会更快乐。

【讨论】:

有趣的想法,但有时解释器的命令历史很有用。你真的每次修改代码都销毁它吗? IPython 怎么样?它保存会话之间的命令历史记录。 使用PYTHONSTARTUP 环境变量会更有意义。 @EricWilson,您可以使用 readline.read_history_file("setup.py") 阅读相同的启动脚本,将所有命令添加到您的历史记录中。 @utapyngo,你不需要 IPython; readline 模块提供了该功能:atexit.register(readline.write_history_file, hist_file),其中hist_file 是您要包含历史记录的文件的名称。【参考方案4】:

看看 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

【讨论】:

谢谢,但我不会使用 IPython 或 Jython。 我很高兴在这里获得赏金,因为发现 IPython 的价值远远超过 50 个代表。谢谢。【参考方案5】:

好的,我不确定是否有资格在不更改代码的情况下作为答案,但是……至少,这不涉及对 module1 的更改。

您可以使用一些模块包装类,它在加载 module1 之前和之后保存加载的模块,并提供 reload 方法,类似这样:

import sys

class Reloader(object):
    def __init__(self, modulename):
        before = sys.modules.keys()
        __import__(modulename)
        after = sys.modules.keys()
        names = list(set(after) - set(before))
        self._toreload = [sys.modules[name] for name in names]

    def do_reload(self):
        for i in self._toreload:
            reload(i)

然后加载module1:

reloader = Reloader('module1')

假设您可以修改 module2 并将其重新加载到解释器中:

reloader.do_reload()

【讨论】:

【参考方案6】:

这是您可以使用的递归重新加载函数(感谢@Matthew): https://***.com/a/17194836/1020625

【讨论】:

这不会解决 OP 询问的问题。该函数通过查找模块中引用其他模块的所有全局变量来工作。这些将由import someModule 之类的语句创建。但是如果你这样做from someModule import something,则不会创建引用模块本身的变量,因此rreload函数不会重新加载someModule 那是真的,我没想过

以上是关于重新加载已导入另一个模块的模块的主要内容,如果未能解决你的问题,请参考以下文章

Python之reload函数

python如何重新加载模块

防止 Python 缓存导入的模块

在python中重新加载模块

在 mocha 测试之间重新导入模块

如何在 utop 中重新加载 OCaml 模块