重新加载被导入到另一个模块的模块。

18

让我们面对现实,修改Python代码后需要重新加载的整个流程相当混乱。我有一段时间前就想到了,用解释器调用import <module>比用from <module> import <class/function>更好,因为这样我可以调用reload(module)来获取更新的代码。

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

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

然后我进入module2并更改代码。 结果发现调用reload(module1)不会重新加载module2中的更改的代码,即使module2的代码在module1顶部被导入。 有没有办法重新加载所有内容而不重启解释器?

在有人对我的样式提出异议之前,我只想说:

  1. 我只从解释器中调用reload,从不在活动代码中调用。 这个问题涉及当我测试新代码时。
  2. 我从不使用<module> import *,我知道那会破坏可读性

1
这里有一个递归重载函数,你可以使用:https://dev59.com/fmUp5IYBdhLWcg3wHUvi#17194836 - AlbertFerras
等一下,那是一个非常好的答案。如果你把它作为答案发表,我会接受它。 - J-bob
那不会实现你想要的 from module import stuff 导入方式。请查看我的回答评论。 - BrenBarn
6个回答

13

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

3
谢谢,但我不会使用IPython或Jython。 - Eric Wilson
1
我很高兴在这里颁发悬赏,因为发现IPython的价值远远超过50个声望值。谢谢。 - Eric Wilson

10

要重新加载一个模块,你需要使用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

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


为了完整起见,这里是 reload 的参考文档:http://docs.python.org/2/library/functions.html#reload - Johannes P

2

好的,我不确定这是否算作一个答案而不需要更改代码,但至少不需要更改 module1 的内容。

可以使用一些模块包装类,在加载模块1之前和之后保存已加载的模块,并提供一个 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()

2

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

$ python -i setup.py
>>>

这将运行setup.py,然后将您留在交互式提示符处。或者,不要在交互式提示符中做太多工作,编写自动化测试来为您完成工作。
你说得对,在Python中重新加载模块很麻烦。语言的语义使得在进程运行时更改代码变得困难。学会不需要重新加载模块,你会更加快乐。

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

1
不要忘记,导入实际上只是在命名空间中分配一个名称。因此,在重新加载后,您可以重新分配该名称:
>>> reload(module2)
>>> module1.class1 = module2.class1

现在,module1内的class1对象指向来自module2重新加载的版本。


0

那不会解决OP所询问的问题。该函数的工作原理是查找模块中引用其他模块的所有全局变量。这些变量是通过类似于“import someModule”的语句创建的。但是,如果您使用“from someModule import something”,则不会创建指向模块本身的变量,因此该“rreload”函数将无法重新加载“someModule”。 - BrenBarn
那是真的,我没有考虑到它。 - AlbertFerras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接