36

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

但是,假设some_module我有import 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 不仅 reload some_module,而且递归地重新加载每个导入的模块some_module(以及每个模块导入的每个模块,等等),这样我可以确定我没有使用任何其他some_module依赖的模块的旧版本。

我认为 Python 中没有任何内置的东西可以像recursive_reload我在这里描述的函数一样,但是有没有一种简单的方法可以将这样的东西组合在一起?

4

10 回答 10

35

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

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

于 2013-06-19T15:08:46.217 回答
4

每次修改完模块后,实际编写一些测试用例并运行它们不是更简单吗?

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

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

于 2013-03-19T17:58:31.037 回答
4

我遇到了同样的问题,我已经建立了@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 允许它们,所以这个 reload 函数也可以处理它们。
  3. 我添加了一个允许重新加载的路径列表(默认为 [''])。一些模块不喜欢以正常方式重新加载(如图所示
于 2015-02-06T04:02:43.360 回答
3

该代码对于导入的依赖模块非常有效import another_module,但是当模块导入函数时它失败了from another_module import some_func

我扩展了@redsk 的答案,试图对这些功能保持聪明。我还添加了一个黑名单,因为不幸的是typing并且importlib没有出现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. 我不知道为什么某些 builtin_module_names 带有下划线前缀(例如collections列为_collections,所以我必须进行双字符串检查。
  2. callable()返回True课程,我想这是意料之中的,但这是我不得不将额外模块列入黑名单的原因之一。

至少现在我能够在运行时深度重新加载一个模块,并且从我的测试中我能够深入多个级别from foo import bar并在每次调用时查看结果rreload()

(为长而丑陋的深度道歉,但黑色格式的版本在 SO 上看起来不太可读)

于 2019-10-02T12:36:44.300 回答
2

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

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")

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

于 2021-03-16T18:32:41.840 回答
1

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

一个.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!
于 2013-03-19T18:12:29.333 回答
1

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

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__))
于 2016-07-07T10:41:36.103 回答
1

对于 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
于 2019-01-25T16:13:40.247 回答
0

下面是我使用的递归重载函数,包括 ipython/jupyter 的一个魔术函数。

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

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)
于 2020-05-15T06:34:04.230 回答
-1

这是一件棘手的事情 - 我在这个答案中有一个工作示例: 如何查找依赖于 python 中特定模块的模块列表

于 2013-03-19T18:00:47.360 回答