reload' 的递归版本
在我写Python代码的时候,通常会在解释器里随便测试一下。我会先用import some_module
导入模块,然后测试一下,发现bug后修复它并保存,然后用内置的reload
函数来reload(some_module)
再测试一次。
不过,假设在some_module
里面我还导入了some_other_module
,当我在测试some_module
的时候发现some_other_module
里也有bug并修复了它。现在调用reload(some_module)
并不会自动重新导入some_other_module
。我必须手动重新导入这个依赖,比如用reload(some_module.some_other_module)
,或者import some_other_module; reload(some_other_module)
。如果我改了很多依赖,搞不清楚哪些需要重新加载,那我就得重启整个解释器。
更方便的做法是如果有一个recursive_reload
函数,我只需要调用recursive_reload(some_module)
,这样Python不仅会重新加载some_module
,还会递归地重新加载some_module
所导入的每一个模块(以及那些模块所导入的每一个模块,依此类推),这样我就能确保不会使用到任何旧版本的依赖模块。
我觉得Python里没有内置的功能能像我描述的recursive_reload
那样工作,但有没有简单的方法可以实现这样的功能呢?
11 个回答
这段代码在以 import another_module
方式导入依赖模块时运行得很好,但当以 from another_module import some_func
导入模块中的函数时就失败了。
我在 @redsk 的回答基础上进行了扩展,试图更聪明地处理这些函数。同时,我还添加了一个黑名单,因为不幸的是,typing
和 importlib
并没有出现在 sys.builtin_module_names
中(可能还有其他模块也没有)。我还想防止一些我已知的依赖被重新加载。
我还跟踪了重新加载的模块名称,并将它们返回。
在 Windows 上使用 Python 3.7.4 测试过:
def rreload(module, paths=None, mdict=None, base_module=None, blacklist=None, reloaded_modules=None):
"""Recursively reload modules."""
if paths is None:
paths = [""]
if mdict is None:
mdict = {}
if module not in mdict:
# modules reloaded from this module
mdict[module] = []
if base_module is None:
base_module = module
if blacklist is None:
blacklist = ["importlib", "typing"]
if reloaded_modules is None:
reloaded_modules = []
reload(module)
reloaded_modules.append(module.__name__)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType and attribute.__name__ not in blacklist:
if attribute not in mdict[module]:
if attribute.__name__ not in sys.builtin_module_names:
if os.path.dirname(attribute.__file__) in paths:
mdict[module].append(attribute)
reloaded_modules = rreload(attribute, paths, mdict, base_module, blacklist, reloaded_modules)
elif callable(attribute) and attribute.__module__ not in blacklist:
if attribute.__module__ not in sys.builtin_module_names and f"_{attribute.__module__}" not in sys.builtin_module_names:
if sys.modules[attribute.__module__] != base_module:
if sys.modules[attribute.__module__] not in mdict:
mdict[sys.modules[attribute.__module__]] = [attribute]
reloaded_modules = rreload(sys.modules[attribute.__module__], paths, mdict, base_module, blacklist, reloaded_modules)
reload(module)
return reloaded_modules
一些备注:
- 我不知道为什么有些内置模块名称前面会有一个下划线(比如
collections
被列为_collections
,所以我必须进行双重字符串检查。) callable()
对于类返回True
,我想这是正常的,但这也是我需要将额外模块加入黑名单的原因之一。
至少现在我能够在运行时深度重新加载一个模块,并且通过我的测试,我可以使用 from foo import bar
多次深入,并在每次调用 rreload()
时看到结果。
(抱歉内容有点长且不太美观,但黑色格式的版本在 SO 上看起来不太易读。)
我遇到了同样的问题,并在@Mattew和@osa的回答基础上做了一些改进。
from types import ModuleType
import os, sys
def rreload(module, paths=None, mdict=None):
"""Recursively reload modules."""
if paths is None:
paths = ['']
if mdict is None:
mdict = {}
if module not in mdict:
# modules reloaded from this module
mdict[module] = []
reload(module)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType:
if attribute not in mdict[module]:
if attribute.__name__ not in sys.builtin_module_names:
if os.path.dirname(attribute.__file__) in paths:
mdict[module].append(attribute)
rreload(attribute, paths, mdict)
reload(module)
#return mdict
这里有三个不同之处:
- 一般情况下,reload(module) 也需要在函数的最后调用,正如 @osa 指出的那样。
- 如果有循环导入的情况,之前的代码会一直循环下去,所以我添加了一个字典来记录其他模块加载的模块集合。虽然循环依赖不是个好事,但Python是允许的,所以这个重载函数也能处理这种情况。
- 我添加了一些路径的列表(默认是 ['']),这些路径是允许重载的。有些模块不喜欢以正常方式被重载(具体情况可以在这里查看)。
我也遇到过同样的问题,你让我有了动力去解决它。
from types import ModuleType
try:
from importlib import reload # Python 3.4+
except ImportError:
# Needed for Python 3.0-3.3; harmless in Python 2.7 where imp.reload is just an
# alias for the builtin reload.
from imp import reload
def rreload(module):
"""Recursively reload modules."""
reload(module)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType:
rreload(attribute)
或者,如果你在使用IPython的话,可以直接用 dreload
,或者在启动时加上 --deep-reload
。