'reload'的递归版本

时间:2013-03-19 17:57:23

标签: python python-module python-import

当我开发Python代码时,我通常会在解释器中以临时方式对其进行测试。我将import some_module,测试它,找到错误,修复错误并保存,然后使用内置的reload函数reload(some_module)再次测试。

但是,假设在some_moduleimport some_other_module,并且在测试some_module时,我发现了some_other_module中的错误并修复了它。现在调用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导入(以及每个模块导入的每个模块,依此类推),以便我可以确定我没有使用some_module所依赖的任何其他模块的旧版本

我认为Python内置的内容与我在这里描述的recursive_reload函数不同,但有没有一种简单的方法可以将这样的东西组合在一起?

10 个答案:

答案 0 :(得分:26)

我遇到了同样的问题,你激励我真正解决问题。

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

答案 1 :(得分:4)

实际编写一些测试用例并在每次完成修改模块时运行它们会不会更简单?

你在做什么很酷(你实质上是使用TDD(测试驱动开发),但你做错了。

考虑到使用书面单元测试(使用默认的python unittest模块,或者更好的是nose),您可以进行 可重用的测试 稳定 ,可帮助您更快,更好地检测代码中的不一致性,而不是在交互式环境中测试模块。

答案 2 :(得分:3)

我遇到了同样的问题而且我已经在@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

有三点不同:

  1. 在一般情况下,必须在函数末尾调用reload(module),就像@osa指出的那样。
  2. 使用循环导入依赖项,之前发布的代码将永远循环,因此我添加了列表字典以跟踪其他模块加载的模块集。虽然循环依赖并不酷,但Python允许它们,所以这个重新加载函数也处理它们。
  3. 我添加了允许重新加载的路径列表(默认为[''])。有些模块不是以正常方式重新加载的(如图here所示)。

答案 3 :(得分:2)

从技术上讲,在每个文件中都可以放置一个重载命令,以确保每次导入时重新加载

a.py:

def testa():
    print 'hi!'

b.py:

import a
reload(a)
def testb():
    a.testa()

现在,以交互方式:

import b
b.testb()
#hi!

#<modify a.py>

reload(b)
b.testb()
#hello again!

答案 4 :(得分:2)

该代码对于像import another_module一样导入的依赖模块非常有效,但是当模块使用from another_module import some_func导入函数时,代码失败。

我扩展了@redsk的答案,以尝试并明智地使用这些功能。我还添加了一个黑名单,因为不幸的是typingimportlib不在sys.builtin_module_names中出现(也许还有更多)。另外我还想防止重新加载我所知道的某些依赖项。

我还跟踪重新加载的模块名称并返回它们。

在Python 3.7.4 Windows上测试:

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

一些注意事项:

  1. 我不知道为什么某些buildin_module_names带有下划线前缀(例如collections被列为_collections,所以我必须进行双字符串检查。
  2. callable()为类返回True,我想这是预期的,但这是我必须将额外模块列入黑名单的原因之一。

至少现在我可以在运行时深度重新加载模块,并且通过测试,我可以深入from foo import bar到多个级别,并在每次调用rreload()时看到结果

(对冗长而丑陋的代码表示歉意,但黑色格式的版本在SO上看起来不太可读)

答案 5 :(得分:1)

我发现redsk的答案非常有用。 我提出了一个简化的(对于用户而不是代码)版本,其中自动收集模块的路径,并且递归适用于任意数量的级别。 一切都是自成一体的。 在Python 3.4上测试过。我想对于python 3.3,必须import reload from imp而不是... from importlib。 它还检查__file__文件是否存在,如果编码器忘记在子模块中定义__init__.py文件,则该文件可能为false。在这种情况下,会引发异常。

def rreload(module):
    """
    Recursive reload of the specified module and (recursively) the used ones.
    Mandatory! Every submodule must have an __init__.py file
    Usage:
        import mymodule
        rreload(mymodule)

    :param module: the module to load (the module itself, not a string)
    :return: nothing
    """

    import os.path
    import sys

    def rreload_deep_scan(module, rootpath, mdict=None):
        from types import ModuleType
        from importlib import reload

        if mdict is None:
            mdict = {}

        if module not in mdict:
            # modules reloaded from this module
            mdict[module] = []
        # print("RReloading " + str(module))
        reload(module)
        for attribute_name in dir(module):
            attribute = getattr(module, attribute_name)
            # print ("for attr "+attribute_name)
            if type(attribute) is ModuleType:
                # print ("typeok")
                if attribute not in mdict[module]:
                    # print ("not int mdict")
                    if attribute.__name__ not in sys.builtin_module_names:
                        # print ("not a builtin")
                        # If the submodule is a python file, it will have a __file__ attribute
                        if not hasattr(attribute, '__file__'):
                            raise BaseException("Could not find attribute __file__ for module '"+str(attribute)+"'. Maybe a missing __init__.py file?")

                        attribute_path = os.path.dirname(attribute.__file__)

                        if attribute_path.startswith(rootpath):
                            # print ("in path")
                            mdict[module].append(attribute)
                            rreload_deep_scan(attribute, rootpath, mdict)

    rreload_deep_scan(module, rootpath=os.path.dirname(module.__file__))

答案 6 :(得分:1)

我找到了清除所有模块然后重新导入模块 here 的想法,建议这样做:

import sys
sys.modules.clear()

这会弄乱您不想重新加载的已加载模块(如果您只想重新加载自己的模块)。我的想法是只清除包含您自己文件夹的模块。像这样:

import sys
import importlib

def reload_all():
    delete_folders = ["yourfolder", "yourotherfolder"]

    for module in list(sys.modules.keys()):
        if any(folder in module for folder in delete_folders):
            del sys.modules[module]

    # And then you can reimport the file that you are running.
    importlib.import_module("yourfolder.entrypoint")

重新导入您的入口点将重新导入其所有导入,因为模块已被清除并且它会自动递归。

答案 7 :(得分:0)

对于Python 3.6+,您可以使用:

from types import ModuleType
import sys
import importlib

def deep_reload(m: ModuleType):
    name = m.__name__  # get the name that is used in sys.modules
    name_ext = name + '.'  # support finding sub modules or packages

    def compare(loaded: str):
        return (loaded == name) or loaded.startswith(name_ext)

    all_mods = tuple(sys.modules)  # prevent changing iterable while iterating over it
    sub_mods = filter(compare, all_mods)
    for pkg in sorted(sub_mods, key=lambda item: item.count('.'), reverse=True):
        importlib.reload(sys.modules[pkg])  # reload packages, beginning with the most deeply nested

答案 8 :(得分:0)

下面是我使用的递归重载函数,包括用于ipython / jupyter的magic函数。

它对所有子模块进行深度优先搜索,并以正确的依赖顺序重新加载它们。

import logging
from importlib import reload, import_module
from types import ModuleType
from IPython.core.magic import register_line_magic

logger = logging.getLogger(__name__)


def reload_recursive(module, reload_external_modules=False):
    """
    Recursively reload a module (in order of dependence).

    Parameters
    ----------
    module : ModuleType or str
        The module to reload.

    reload_external_modules : bool, optional

        Whether to reload all referenced modules, including external ones which
        aren't submodules of ``module``.

    """
    _reload(module, reload_external_modules, set())


@register_line_magic('reload')
def reload_magic(module):
    """
    Reload module on demand.

    Examples
    --------
    >>> %reload my_module
    reloading module: my_module

    """
    reload_recursive(module)


def _reload(module, reload_all, reloaded):
    if isinstance(module, ModuleType):
        module_name = module.__name__
    elif isinstance(module, str):
        module_name, module = module, import_module(module)
    else:
        raise TypeError(
            "'module' must be either a module or str; "
            f"got: {module.__class__.__name__}")

    for attr_name in dir(module):
        attr = getattr(module, attr_name)
        check = (
            # is it a module?
            isinstance(attr, ModuleType)

            # has it already been reloaded?
            and attr.__name__ not in reloaded

            # is it a proper submodule? (or just reload all)
            and (reload_all or attr.__name__.startswith(module_name))
        )
        if check:
            _reload(attr, reload_all, reloaded)

    logger.debug(f"reloading module: {module.__name__}")
    reload(module)
    reloaded.add(module_name)

答案 9 :(得分:-1)

这是一个棘手的事情 - 我在这个答案中有一个有效的例子: how to find list of modules which depend upon a specific module in python