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


797

我有一台运行时间较长的Python服务器,并且希望能够在不重新启动服务器的情况下升级服务。最好的方法是什么?

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

53
备注:“导入”并不意味着“加载”,它的意思是“如果尚未加载,则加载然后导入到名称空间中”。
科斯(Kos)

3
问题不应该包含“卸载”,因为这在python中尚无法实现-然而,重新加载是已知的范例,如下所示
robertmoggach 2014年

在py2exe应用程序中使用动态模块时,我遇到了同样的问题。由于py2exe始终将字节码保留在zip目录中,因此无法重新加载。但是我发现了一个使用import_file模块的可行解决方案。现在我的应用程序运行正常。
Pritam Pan'3

2
如果由于代码正在尝试删除.pyc文件而要“卸载”怎么办?
darkgaze

Answers:


788

您可以使用reload内置函数(仅适用于Python 3.4+)重新导入已导入的模块:

from importlib import reload  
import foo

while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)

在Python 3中,reload已移至imp模块。在3.4中,imp不推荐使用importlib,而reload在中添加了。当定位到3或更高版本时,在调用reload或导入它时参考相应的模块。

我认为这就是您想要的。诸如Django开发服务器之类的Web服务器都使用此服务器,这样您就可以查看代码更改的效果,而无需重新启动服务器进程本身。

引用文档:

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

正如您在问题中指出的那样,Foo如果Foo类驻留在foo模块中,则必须重构对象。


10
实际上,当您更改文件时,django开发服务器会自动重新启动..(它重新启动服务器,而不仅仅是重新加载模块)
hasen

25
这个“ is_changed”函数从哪里来?我没有看到任何文档,它也没有在我的Python 3.1.3环境中运行,也没有在2.6.4中运行。
jedmao 2011年

5
没有教养,Django不能只使用reload:pyunit.sourceforge.net/notes/reloading.html
raylu 2012年

14
@BartoszKP如果X不是模块,则可以import sys; reload(sys.modules[X.__module__])
drevicko '16

5
@jedmao @JamesDraper我很确定该is_changed函数只是您必须编写的任意函数;它不是内置的。例如,它可能会打开与您要导入的模块相对应的文件,并将其与缓存版本进行比较以查看其是否已更改。
James Mchugh,

252

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

BDFL已经回答了这个问题。

但是,imp在3.4中已弃用,importlib改为(感谢@Stefan!)。

因此,importlib.reload(module)尽管我不确定,但您现在应该使用。


23
认真的新手感谢Python 2和3之间的了解至关重要的细微差别
Smandoli

24
imp.reload(imp)有效吗?
卢瓦克福雷-拉克鲁瓦

2
@LoïcFaure-Lacroix同样的方法reload(__builtins__)在2.x中有效
JBernardo 2012年

1
@Tarrasch:这是您要重新加载的Python模块,例如问题中的示例。
Paul D. Waite,2013年

3
@LoïcFaure-Lacroix是的,imp可以重新加载自身。
Devyn Collier Johnson

92

如果模块不是纯Python,则删除模块可能会特别困难。

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

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

>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3

大于3的数字表示很难摆脱该模块。本地的“空”(不包含任何内容)模块应在之后收集垃圾

>>> del sys.modules["empty"]
>>> del empty

作为第三个引用是getrefcount()函数的构件。


4
我刚刚发现,如果模块是软件包的一部分,则也必须将其删除:setattr(package, "empty", None)
u0b34a0f6ae

6
这是正确的解决方案,尤其是在您的程序包带有嵌套模块的情况下。reload()仅重新加载最顶层的模块,并且其中的任何内容都不会重新加载,除非您首先从sys.modules中删除它。
塞林2016年

73

reload(module),但前提是它是完全独立的。如果还有其他引用该模块(或属于该模块的任何对象)的引用,则您将得到细微而奇怪的错误,这些错误是由于旧代码的停留时间超出您的预期而导致的,并且isinstance无法在不同版本的相同的代码。

如果您具有单向依赖关系,则还必须重新加载所有依赖于重新加载的模块的模块,以摆脱对旧代码的所有引用。然后递归依赖于重新加载的模块重新加载模块。

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

在这种情况下,唯一的方法是hack sys.modules,这是不受支持的。您必须仔细检查并删除sys.modules要在下次导入时重新加载的每个条目,还必须删除其值None用于处理实现问题的条目,以缓存失败的相对导入。它不是很好,但是只要您有一套完全独立的依赖项,并且不会将引用保留在其代码库之外,那么它就是可行的。

最好重新启动服务器。:-)


1
dreload不是专门针对这种情况吗?
乔什(Josh)2012年

@Josh:是的,它是用于重新加载程序包树,即使这样,它仅在程序包没有外部/循环依赖关系时才起作用。
bobince 2012年

1
您是否可以使用None值来详细说明部分,因为我正好遇到了这个问题:我要从sys.modules重新导入中删除项目,然后在重新导入后删除一些导入的依赖项None
schlamar 2013年

@shclamar:请参阅stackoverflow.com/questions/1958417/…(以及那里的链接)以获取背景信息。我不清楚(即使查看import.c代码)None删除“真实”条目后,条目如何设法通过导入机制返回,我似乎无法在2.7上实现。将来肯定不再是问题,因为隐性相对进口已经消失。同时,删除所有带有None值的条目似乎可以解决此问题。
bobince 2013年

1
@Eliethesaiyan:你的意思是reload功能吗?它是内置的,您无需导入任何库。
bobince '16

63
if 'myModule' in sys.modules:  
    del sys.modules["myModule"]

3
+1。我的目标是在python中运行鼻子测试。在我加载了一个模块并重命名了一些函数之后,调用时仍然保留旧名称nose.run(),即使在reload(my_module) %run my_module
Peter D

5
如果您的模块导入了自己的子模块,则可能也需要删除它们。有点像[del(sys.modules[mod] for mod in sys.modules.keys() if mod.startswith('myModule.')]
drevicko 2014年

我不认为这会卸载模块。在Python 3.8:import sys; import json; del sys.modules['json']; print(json.dumps([1]))和json模块仍在工作,即使它不再在sys.modules中。
Seperman

60

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

reload(module)

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

import imp
imp.reload(module)

但是从3.4版开始imp 不推荐使用importlib,所以请使用:

import importlib
importlib.reload(module)

要么

from importlib import reload
reload(module)

2
处理以下任何一种情况:(当然from six import reload_module需要pip install six首先处理)
Anentropic

@Anentropic:最好使用六个软件包,但语法为from six.moves import reload_moduledoc
x0s

23

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

try:
    reload
except NameError:
    # Python 3
    from imp import reload

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


18

接受的答案不处理from X import 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")

在重载的情况下,我们将顶级名称重新分配给新重载的模块中存储的值,从而更新它们。


注意到一个问题,globals()引用您在其中定义此函数的模块,因此,如果您在不同于您在其中调用的模块的模块中定义它,则将不起作用。
约瑟夫·加文2014年

要进行互动,请在>>> from X import Y重新加载后做>>> __import__('X', fromlist='Y')
Bob Stein

@ BobStein-VisiBone,有什么方法可以使它在何时工作fromlist='*'
Mike C

好问题,不知道@MikeC。顺便说一下,我倾向于停止from在import语句中几乎所有使用。import <package>代码中只有明显的package.symbol。意识到这可能并不总是可能的或不希望的。(这里是一个例外:来自将来的import print_function。)
Bob Stein

迈克C:对我foo = reload(foo); from foo import *
有用的

16

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

from importlib import reload

如果要支持3.5之前的Python版本,请尝试以下操作:

from sys import version_info
if version_info[0] < 3:
    pass # Python 2 has built in reload
elif version_info[0] == 3 and version_info[1] <= 4:
    from imp import reload # Python 3.0 - 3.4 
else:
    from importlib import reload # Python 3.5+

要使用它,请运行reload(MODULE),并替换MODULE为要重新加载的模块。

例如,reload(math)将重新加载math模块。


4
或者只是做from importlib import reload。那你就可以做reload(MODULE_NAME)。不需要此功能。
跳马

我相信,modulereload(MODULE_NAME)这不仅仅是公正的解释,reload(MODULE_NAME)而且与其他职能发生冲突的机会也较小。
Richie Bendall '18

@RichieBendall对不起,但是这个答案是完全错误的。reload()函数采用模块对象,而不是模块名称...阅读文档:docs.python.org/3/library/importlib.html#importlib.reload我同意@ pault-这种“作为modulereload”是多余的。
mbdevpl

我更改了答案以反映您的意见。
Richie Bendall

13

如果您不在服务器中,但是正在开发并且需要经常重新加载模块,那么这里是个不错的提示。

首先,请确保您使用的是Jupyter Notebook项目中出色的IPython shell。安装Jupyter后,你可以启动它ipython,或者jupyter console,甚至更好,jupyter qtconsole,这将为您提供一个漂亮的彩色控制台,并在任何OS中均具有代码完成功能。

现在在您的外壳中,键入:

%load_ext autoreload
%autoreload 2

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

除了2,自动重载魔术还有其他选择

%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.

7

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

   for mod in sys.modules.values():
      reload(mod)

有关更多信息,请参见重新加载Python模块


实际上,这似乎并不可靠(在2.6版中),因为其中的所有内容都不都是sys.modules.values()模块。例如:>>> type(sys.modules.values()[1])<class'email.LazyImporter'>因此,如果我尝试运行该代码,它就会崩溃(我知道这并不意味着是一个实际的解决方案,只是指出这一点)。
弗朗西斯·戴维

如所写,它甚至在早期的python中也不起作用。我不得不排除一些名字。当我将该代码移到新计算机上时,我将更新该帖子。

2
经过一些修改后,在Python 2.7中可以正常工作:if mod and mod.__name__ != "__main__": imp.reload(mod)
Czarek Tomczak 2012年

2
这对我来说效果很好:import imp [如果在m .__ name而不是imp.is_builtin(m .__ name__)中包含m而不是“ ”,则导入imp [在sys.modules.values()中为m重新加载(m )]
Patrick Wolf

5

追求特质有一个可以很好地完成此任务的模块。https://traits.readthedocs.org/zh/4.3.0/_modules/traits/util/refresh.html

它将重新加载已更改的所有模块,并更新正在使用该模块的其他模块和实例对象。大多数情况下它不起作用__very_private__方法使用,并且可能会阻塞类继承,但是它为我节省了编写PyQt guis或在Maya或Nuke等程序中运行的东西时不必重新启动主机应用程序的疯狂时间。它可能在20%到30%的时间内无效,但是仍然非常有用。

Enthought的软件包不会在文件更改时立即重新加载文件-您必须明确地调用它-但是如果您真的需要它,那么实现起来应该不那么困难


5

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

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



3

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

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

from IPython.lib.deepreload import reload as dreload
dreload(foo)

如果您没有Jupyter,请在外壳程序中使用以下命令将其安装:

pip3 install jupyter

importlib的Ipython dreload和reload()都抱怨reload() argument must be module。我正在使用自定义函数导入,但似乎无法正常工作。使用内置模块确实有效。:-(对于我对我的代码所做的每一个小改动,重新加载iPython都是浪费时间……
m3nda

2

编辑(答案V2)

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

请注意,这不会如果GC已关闭,或者重新加载了不受GC监视的数据,则。如果您不想弄乱GC,那么原始答案可能就足够了。

新代码:

import importlib
import inspect
import gc
from weakref import ref


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)

原始答案

就像@bobince的答案中所写,如果另一个模块中已经存在对该模块的引用(特别是如果它是使用as诸如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

注意:小心使用!在非外围模块(例如,定义外部使用的类的模块)上使用它们可能会导致Python内部发生问题(例如,酸洗/不酸洗问题)。


1

对我而言,Abaqus就是这种方式。假设您的文件是Class_VerticesEdges.py

sys.path.append('D:\...\My Pythons')
if 'Class_VerticesEdges' in sys.modules:  
    del sys.modules['Class_VerticesEdges']
    print 'old module Class_VerticesEdges deleted'
from Class_VerticesEdges import *
reload(sys.modules['Class_VerticesEdges'])

这个答案是从这里直接获得的副本:ebanshi.cc/questions/1942/…–
SiHa,

0

尝试在Sublime Text中重新加载某些内容时遇到了很多麻烦,但最终我可以编写此实用程序,根据代码在Sublime Text上重新加载模块 sublime_plugin.py用于重新加载模块重新加载模块。

下面的内容允许您从路径上带有空格的模块中重新加载模块,然后在重新加载之后,您可以照常导入。

def reload_module(full_module_name):
    """
        Assuming the folder `full_module_name` is a folder inside some
        folder on the python sys.path, for example, sys.path as `C:/`, and
        you are inside the folder `C:/Path With Spaces` on the file 
        `C:/Path With Spaces/main.py` and want to re-import some files on
        the folder `C:/Path With Spaces/tests`

        @param full_module_name   the relative full path to the module file
                                  you want to reload from a folder on the
                                  python `sys.path`
    """
    import imp
    import sys
    import importlib

    if full_module_name in sys.modules:
        module_object = sys.modules[full_module_name]
        module_object = imp.reload( module_object )

    else:
        importlib.import_module( full_module_name )

def run_tests():
    print( "\n\n" )
    reload_module( "Path With Spaces.tests.semantic_linefeed_unit_tests" )
    reload_module( "Path With Spaces.tests.semantic_linefeed_manual_tests" )

    from .tests import semantic_linefeed_unit_tests
    from .tests import semantic_linefeed_manual_tests

    semantic_linefeed_unit_tests.run_unit_tests()
    semantic_linefeed_manual_tests.run_manual_tests()

if __name__ == "__main__":
    run_tests()

如果是第一次运行,则应该加载该模块,但是如果以后可以再次使用该方法/功能run_tests(),它将重新加载测试文件。使用Sublime Text(Python 3.3.6)会发生很多事情,因为它的解释器永远不会关闭(除非您重新启动Sublime Text,即Python3.3解释器)。


-1

另一种方法是将模块导入功能中。这样,当函数完成时,模块将收集垃圾。


4
该模块将永远不会收集垃圾,因为全局引用至少保存在中sys.modules
所有工人都是必不可少的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.