如何卸载(重新加载)Python模块?

Mar*_*son 759 python module reload python-import

我有一个长期运行的Python服务器,并希望能够在不重新启动服务器的情况下升级服务.这样做的最佳方法是什么?

if foo.py has changed:
    unimport foo  <-- How do I do this?
    import foo
    myfoo = foo.Foo()
Run Code Online (Sandbox Code Playgroud)

cdl*_*ary 734

您可以使用reload内置函数在已导入模块时重新加载模块:

from importlib import reload  # Python 3.4+ only.
import foo

while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)
Run Code Online (Sandbox Code Playgroud)

在Python 3中,reload被移动到imp模块.在3.4中,imp被弃用赞成importlib,reload并被添加到后者中.当定位3或更高版本时,要么在调用时引用相应的模块,要么reload导入它.

我认为这就是你想要的.像Django的开发服务器这样的Web服务器使用它,这样您就可以看到代码更改的效果,而无需重新启动服务器进程本身.

引用文档:

重新编译Python模块的代码并重新执行模块级代码,定义一组新的对象,这些对象绑定到模块字典中的名称.扩展模块的init功能不是第二次调用.与Python中的所有其他对象一样,只有在引用计数降为零后才会回收旧对象.模块名称空间中的名称将更新为指向任何新对象或已更改的对象.对旧对象的其他引用(例如模块外部的名称)不会反弹以引用新对象,如果需要,必须在每个命名空间中进行更新.

正如您在问题中提到的,Foo如果Foo类位于foo模块中,您将不得不重建对象.

  • 不适用于`from m import X`案例. (24认同)
  • 这个"is_changed"函数来自哪里?我没有看到它的文档,它不在我的Python 3.1.3环境中运行,也不在2.6.4中运行. (23认同)
  • @BartoszKP如果`X`不是模块,你可以`导入sys; 重载(sys.modules中[X .__模块__])` (14认同)
  • 实际上,当您更改文件时,django dev服务器会重新启动...(它重新启动服务器,而不仅仅是重新加载模块) (10认同)
  • 没有cdleary,Django不能只使用重新加载:http://pyunit.sourceforge.net/notes/reloading.html (4认同)
  • 对于具有依赖项的模块,重新加载是不够的.请参阅下面的博客:http://stackoverflow.com/a/438845/456878.这曾经让我感到困惑,浪费了10分钟. (4认同)
  • 仅供参考,重装是邪恶的根源.例如,使用第一个导入创建的对象的所有实例都将引用旧类:`import foo; bar = foo.Bar(); 重新加载(FOO); assert isinstance(bar,foo.Bar)`将引发AssertError (3认同)
  • @jedmao @JamesDraper我很确定`is_changed`函数只是一个你必须编写的任意函数; 它不是内置的.例如,它可能会打开与您要导入的模块对应的文件,并使用缓存版本对其进行区分,以查看它是否已更改. (3认同)
  • 如果类/函数是通过“from MyFile import *”加载的怎么办?如何重新加载这个? (3认同)
  • 奇怪.答案仅涉及重新加载.卸货怎么样? (2认同)
  • 如何使用reload进行导入,例如"从abc import someClass as abcClass"... reload(abcClass)抱怨它没有定义. (2认同)
  • 这个问题的最佳答案如何?它在我的py27和/或py34中不起作用@jedmao你有没有发现"is_changed"的来源? (2认同)
  • @JamesDraper nope,从未发现'is_changed`来自哪里......或者我忘记了.这是很久以前的事了. (2认同)

Pau*_*ite 245

在Python 3.0-3.3中,您将使用: imp.reload(module)

BDFL已经回答了这个问题.

但是,imp在3.4中被弃用,赞成importlib(感谢@Stefan!).

因此,我认为你现在使用了importlib.reload(module),虽然我不确定.

  • imp.reload(imp)有效吗? (24认同)
  • 认真的新手很高兴了解Python 2和3之间的关键细微差别. (22认同)
  • @LoïcFaure-Lacroix是的,imp可以重装自己. (3认同)
  • @LoïcFaure-Lacroix以同样的方式`reload(__ builtins __)`在2.x中有效 (2认同)

Gre*_*ind 88

如果模块不是纯Python,则删除模块尤其困难.

以下是一些信息:如何真正删除导入的模块?

您可以使用sys.getrefcount()来查找实际的引用数.

>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3
Run Code Online (Sandbox Code Playgroud)

大于3的数字表示很难摆脱该模块.本土的"空"(不含任何)模块应该在之后进行垃圾收集

>>> del sys.modules["empty"]
>>> del empty
Run Code Online (Sandbox Code Playgroud)

因为第三个引用是getrefcount()函数的工件.

  • 这是正确的解决方案,特别是如果您有一个包含嵌套模块的包.`reload()`只重新加载最顶层的模块,除非你先从sys.modules中删除它,否则不会重新加载其中的任何内容. (5认同)
  • 我刚刚发现如果模块是包的一部分,你也必须在那里删除它:`setattr(package,"empty",None)` (4认同)

bob*_*nce 67

reload(module),但只有它完全是独立的.如果其他任何东西都有对模块(或属于该模块的任何对象)的引用,那么你会得到由旧代码挂起比你预期的更长时间引起的细微和好奇的错误,以及isinstance不能在不同版本的相同的代码.

如果您有单向依赖项,则还必须重新加载依赖于重新加载的模块的所有模块,以去除对旧代码的所有引用.然后递归地重新加载依赖于重新加载的模块的模块.

如果您具有循环依赖关系(例如,在处理重新加载包时非常常见),则必须一次性卸载组中的所有模块.您无法执行此操作,reload()因为它将在刷新依赖项之前重新导入每个模块,从而允许旧引用进入新模块.

在这种情况下,唯一的方法是破解sys.modules,这是一种不受支持的.您必须通过并删除sys.modules下次导入时要重新加载的每个条目,并删除其值为None处理缓存失败的相对导入的实现问题的条目.它并不是非常好,但只要你有一套完全自包含的依赖项,不会将引用留在代码库之外,它就可以使用了.

最好重启服务器.:-)


小智 61

if 'myModule' in sys.modules:  
    del sys.modules["myModule"]
Run Code Online (Sandbox Code Playgroud)

  • 如果您的模块导入它自己的子模块,您可能也需要删除它们.像`[del(sys.modules [mod] for mod in sys.modules.keys()中的东西,如果mod.startswith('myModule.')]`. (5认同)
  • +1.我的目标是在python中运行鼻子测试.在我加载了一个模块并重命名了一些函数之后,在调用`nose.run()`时,旧名称仍然存在,即使在`reload(my_module)``%run my_module`之后也是如此. (3认同)
  • 我不认为这会卸载模块。在 Python 3.8 上:`import sys; 导入 json;del sys.modules['json']; print(json.dumps([1]))` 和 json 模块仍然有效,即使它不再位于 sys.modules 中。 (2认同)

goe*_*tzc 56

对于Python 2,使用内置函数reload():

reload(module)
Run Code Online (Sandbox Code Playgroud)

对于Python 2和3.2-3.3,使用从模块imp重新加载:

import imp
imp.reload(module)
Run Code Online (Sandbox Code Playgroud)

imp 不推荐使用,因为3.4版本支持导入库中,所以使用:

import importlib
importlib.reload(module)
Run Code Online (Sandbox Code Playgroud)

要么

from importlib import reload
reload(module)
Run Code Online (Sandbox Code Playgroud)

  • 处理以下任何一种情况:`来自六个import reload_module`(首先需要`pip install six`) (2认同)

Mat*_*son 21

以下代码允许您兼容Python 2/3:

try:
    reload
except NameError:
    # Python 3
    from imp import reload
Run Code Online (Sandbox Code Playgroud)

您可以reload()在两个版本中使用它,这使事情变得更简单.


Jos*_*vin 17

接受的答案不处理来自X导入Y的情况.此代码处理它和标准导入案例:

def importOrReload(module_name, *names):
    import sys

    if module_name in sys.modules:
        reload(sys.modules[module_name])
    else:
        __import__(module_name, fromlist=names)

    for name in names:
        globals()[name] = getattr(sys.modules[module_name], name)

# use instead of: from dfly_parser import parseMessages
importOrReload("dfly_parser", "parseMessages")
Run Code Online (Sandbox Code Playgroud)

在重新加载的情况下,我们将顶级名称重新分配给新重新加载的模块中存储的值,这些值会更新它们.


Ric*_*all 15

这是重新加载模块的现代方法:

from importlib import reload
Run Code Online (Sandbox Code Playgroud)

只需键入reload(MODULE),替换MODULE为要重新加载的模块的名称.

例如,reload(math)将重新加载数学函数.

  • 或者只是`来自importlib import reload`.然后你可以做`reload(MODULE_NAME)`.不需要此功能. (4认同)

nev*_*ves 13

如果您不在服务器中,但正在开发并需要经常重新加载模块,这里有一个很好的提示.

首先,确保您使用的是Jupyter Notebook项目中出色的IPython shell.安装Jupyter后,您可以使用ipython,或者jupyter console甚至更好地启动它jupyter qtconsole,这将为您提供一个漂亮的彩色控制台,在任何操作系统中都可以完成代码.

现在在shell中,键入:

%load_ext autoreload
%autoreload 2
Run Code Online (Sandbox Code Playgroud)

现在,每次运行脚本时,都会重新加载模块.

除此之外2,还有autoreload魔术的其他选项:

%autoreload
Reload all modules (except those excluded by %aimport) automatically now.

%autoreload 0
Disable automatic reloading.

%autoreload 1
Reload all modules imported with %aimport every time before executing the Python code typed.

%autoreload 2
Reload all modules (except those excluded by %aimport) every time before
executing the Python code typed.
Run Code Online (Sandbox Code Playgroud)


小智 7

对于那些想要卸载所有模块的人(当在Emacs下的Python解释器中运行时):

   for mod in sys.modules.values():
      reload(mod)
Run Code Online (Sandbox Code Playgroud)

更多信息在重新加载Python模块中.

  • 经过一些修改后,在Python 2.7中运行良好:`if mod and mod .__ name _!="_ _ main__":imp.reload(mod)` (2认同)
  • 这对我来说效果很好:如果m而不是"__"在m .__ name__而不是imp.is_builtin(m .__ name__),则导入imp [在sys.modules.values()中为m重新加载(m)m (2认同)

EZL*_*ner 6

编辑(答案V2)

之前的解决方案仅适用于获取重置信息,但它不会更改所有引用(多于reload但少于所需的引用)。为了实际设置所有引用,我必须进入垃圾收集器,并在那里重写引用。现在它就像一个魅力!

请注意,如果 GC 关闭,或者重新加载不受 GC 监控的数据,则此操作将不起作用。如果你不想搞乱GC,原来的答案可能对你来说就足够了。

新代码:

import importlib
import inspect
import gc
from enum import EnumMeta
from weakref import ref


_readonly_attrs = {'__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__',
               '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__',
               '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__',
               '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__',
               '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__',
               '__subclasshook__', '__weakref__', '__members__', '__mro__', '__itemsize__', '__isabstractmethod__',
               '__basicsize__', '__base__'}


def reset_module(module, inner_modules_also=True):
    """
    This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
    module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
    to be the reloaded-module's
    :param module: The module to reload (module reference, not the name)
    :param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
    """

    # For the case when the module is actually a package
    if inner_modules_also:
        submods = {submod for _, submod in inspect.getmembers(module)
                   if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
        for submod in submods:
            reset_module(submod, True)

    # First, log all the references before reloading (because some references may be changed by the reload operation).
    module_tree = _get_tree_references_to_reset_recursively(module, module.__name__)

    new_module = importlib.reload(module)
    _reset_item_recursively(module, module_tree, new_module)


def _update_referrers(item, new_item):
    refs = gc.get_referrers(item)

    weak_ref_item = ref(item)
    for coll in refs:
        if type(coll) == dict:
            enumerator = coll.keys()
        elif type(coll) == list:
            enumerator = range(len(coll))
        else:
            continue

        for key in enumerator:

            if weak_ref_item() is None:
                # No refs are left in the GC
                return

            if coll[key] is weak_ref_item():
                coll[key] = new_item

def _get_tree_references_to_reset_recursively(item, module_name, grayed_out_item_ids = None):
    if grayed_out_item_ids is None:
        grayed_out_item_ids = set()

    item_tree = dict()
    attr_names = set(dir(item)) - _readonly_attrs
    for sub_item_name in attr_names:

        sub_item = getattr(item, sub_item_name)
        item_tree[sub_item_name] = [sub_item, None]

        try:
            # Will work for classes and functions defined in that module.
            mod_name = sub_item.__module__
        except AttributeError:
            mod_name = None

        # If this item was defined within this module, deep-reset
        if (mod_name is None) or (mod_name != module_name) or (id(sub_item) in grayed_out_item_ids) \
                or isinstance(sub_item, EnumMeta):
            continue

        grayed_out_item_ids.add(id(sub_item))
        item_tree[sub_item_name][1] = \
            _get_tree_references_to_reset_recursively(sub_item, module_name, grayed_out_item_ids)

    return item_tree


def _reset_item_recursively(item, item_subtree, new_item):

    # Set children first so we don't lose the current references.
    if item_subtree is not None:
        for sub_item_name, (sub_item, sub_item_tree) in item_subtree.items():

            try:
                new_sub_item = getattr(new_item, sub_item_name)
            except AttributeError:
                # The item doesn't exist in the reloaded module. Ignore.
                continue

            try:
                # Set the item
                _reset_item_recursively(sub_item, sub_item_tree, new_sub_item)
            except Exception as ex:
                pass

    _update_referrers(item, new_item)
Run Code Online (Sandbox Code Playgroud)

原答案

正如 @bobince 的答案中所写,如果另一个模块中已经存在对该模块的引用(特别是使用as关键字 like导入import numpy as np),则该实例将不会被覆盖。

当应用需要配置模块的“全新”状态的测试时,这对我来说是一个很大的问题,因此我编写了一个名为的函数,reset_module它使用importlibreload函数并递归地覆盖所有声明的模块的属性。它已经使用 Python 3.6 版本进行了测试。

import importlib
import inspect
from enum import EnumMeta

_readonly_attrs = {'__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__',
               '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__',
               '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__',
               '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__',
               '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__',
               '__subclasshook__', '__weakref__', '__members__', '__mro__', '__itemsize__', '__isabstractmethod__',
               '__basicsize__', '__base__'}


def reset_module(module, inner_modules_also=True):
    """
    This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
    module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
    to be the reloaded-module's
    :param module: The module to reload (module reference, not the name)
    :param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
    """

    new_module = importlib.reload(module)

    reset_items = set()

    # For the case when the module is actually a package
    if inner_modules_also:
        submods = {submod for _, submod in inspect.getmembers(module)
                   if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
        for submod in submods:
            reset_module(submod, True)

    _reset_item_recursively(module, new_module, module.__name__, reset_items)


def _reset_item_recursively(item, new_item, module_name, reset_items=None):
    if reset_items is None:
        reset_items = set()

    attr_names = set(dir(item)) - _readonly_attrs

    for sitem_name in attr_names:

        sitem = getattr(item, sitem_name)
        new_sitem = getattr(new_item, sitem_name)

        try:
            # Set the item
            setattr(item, sitem_name, new_sitem)

            try:
                # Will work for classes and functions defined in that module.
                mod_name = sitem.__module__
            except AttributeError:
                mod_name = None

            # If this item was defined within this module, deep-reset
            if (mod_name is None) or (mod_name != module_name) or (id(sitem) in reset_items) \
                    or isinstance(sitem, EnumMeta):  # Deal with enums
                continue

            reset_items.add(id(sitem))
            _reset_item_recursively(sitem, new_sitem, module_name, reset_items)
        except Exception as ex:
            raise Exception(sitem_name) from ex
Run Code Online (Sandbox Code Playgroud)

注意:谨慎使用!在非外围模块(例如,定义外部使用的类的模块)上使用这些可能会导致 Python 中的内部问题(例如 pickling/un-pickling 问题)。


小智 5

Enthought Traits有一个适用于此的模块.https://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html

它将重新加载已更改的任何模块,并更新正在使用它的其他模块和实例化对象.它在大多数情况下都不能用于__very_private__方法,并且可以阻止类继承,但它在编写PyQt guis时需要重新启动宿主应用程序,或者在Maya或Nuke等程序中运行的东西,这使我节省了大量时间.它可能在20-30%的时间内不起作用,但它仍然非常有用.

Enthought的软件包在它们改变的那一刻不会重新加载文件 - 你必须明确地称它为 - 但如果你真的需要它,那就不应该很难实现


nev*_*ves 5

其他选择。请注意,Python 默认importlib.reload只会重新导入作为参数传递的库。它不会重新加载您的 lib 导入的库。如果您更改了很多文件并且需要导入一个有些复杂的包,则必须进行深度重新加载

如果您安装了IPythonJupyter,您可以使用一个函数来深度重新加载所有库:

from IPython.lib.deepreload import reload as dreload
dreload(foo)
Run Code Online (Sandbox Code Playgroud)

如果您没有 Jupyter,请在 shell 中使用以下命令安装它:

pip3 install jupyter
Run Code Online (Sandbox Code Playgroud)


Pyt*_*Man 5

那些正在使用python 3并从importlib重新加载的人。

如果您遇到问题,例如似乎模块无法重新加载...那是因为它需要一些时间来重新编译pyc(最多60秒)。我编写此提示只是想知道您是否遇到过此类问题。