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

2024-11-21 08:33:00
admin
原创
5
摘要:问题描述:我有一个长期运行的 Python 服务器,希望能够在不重启服务器的情况下升级服务。最好的方法是什么?if foo.py has changed: unimport foo <-- How do I do this? import foo myfoo = foo.Foo...

问题描述:

我有一个长期运行的 Python 服务器,希望能够在不重启服务器的情况下升级服务。最好的方法是什么?

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

解决方案 1:

当模块已经被导入时,你可以使用以下命令重新加载它importlib.reload()

from importlib import reload  # Python 3.4+
import foo

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

在 Python 2 中,reload是内置函数。在 Python 3 中,它被移至模块imp。在 3.4 中,imp已被弃用,取而代之的是importlib。当以 3 或更高版本为目标时,请在调用时引用相应的模块reload或导入它。

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

引用文档中的话:

  • Python 模块的代码被重新编译,模块级代码被重新执行,通过重用最初加载模块的加载器,定义一组新的对象,这些对象与模块字典中的名称绑定在一起。init扩展模块的功能不会被第二次调用。

  • 与 Python 中的所有其他对象一样,旧对象只有在其引用计数降至零后才会被回收。

  • 模块命名空间中的名称已更新以指向任何新的或更改的对象。

  • 对旧对象的其他引用(例如模块外部的名称)不会被重新绑定以引用新对象,并且如果需要,必须在它们出现的每个命名空间中进行更新。

正如您在问题中指出的那样,如果该类位于模块中,则必须重建Foo对象。Foo`foo`

解决方案 2:

在 Python 3.0–3.3 中你可以使用:imp.reload(module)

BDFL已经回答了这个问题。

然而,imp在 3.4 中已被弃用,取而代之的是importlib(感谢@Stefan!)。

因此,我认为importlib.reload(module)您现在会使用,尽管我不确定。

解决方案 3:

如果模块不是纯 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:

reload(module),但前提是它是完全独立的。如果其他任何东西都引用了该模块(或属于该模块的任何对象),那么您将得到微妙而奇怪的错误,这些错误是由旧代码停留的时间比您预期的要长引起的,并且会出现isinstance无法在同一代码的不同版本之间工作的情况。

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

如果您有循环依赖关系(这种情况很常见,例如当您处理重新加载包时),则必须一次性卸载组中的所有模块。您无法这样做,reload()因为它会在刷新依赖项之前重新导入每个模块,从而允许旧引用潜入新模块。

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

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

解决方案 5:

对于 Python 2使用内置函数reload

reload(module)

对于 Python 2 Python 3.2 - 3.3,使用reload模块 imp:

import imp
imp.reload(module)

对于 Python ≥ 3.4imp 已弃用而改为使用importlib,因此请使用以下命令:

import importlib
importlib.reload(module)

或者:

from importlib import reload
reload(module)

总结:

Python ≥ 3.4:importlib.reload(module)

Python 3.2 — 3.3: imp.reload(module)

Python 2:reload(module)

解决方案 6:

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

解决方案 7:

如果您不在服务器上,但正在开发并且需要频繁重新加载模块,这里有一个很好的提示。

首先,确保您使用的是来自 Jupyter Notebook 项目的出色IPython shellipython 。安装 Jupyter 后,您可以使用或启动它,jupyter console或者更好的是 ,jupyter qtconsole这将在任何操作系统中为您提供带有代码完成功能的漂亮彩色控制台。

现在在你的 shell 中输入:

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

当然,它也可以在 Jupyter Notebook 上运行。

解决方案 8:

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

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

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

解决方案 9:

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

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

解决方案 10:

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

from importlib import reload

如果您想支持 Python 3.5 之前的版本,请使用以下命令:

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可以调用模块来重新加载它的方法。例如,reload(math)将重新加载math模块。

解决方案 11:

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

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

更多信息请参阅重新加载 Python 模块

解决方案 12:

编辑(答案 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) \n                or isinstance(sub_item, EnumMeta):
            continue

        grayed_out_item_ids.add(id(sub_item))
        item_tree[sub_item_name][1] = \n            _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关键字 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) \n                    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 中的内部问题(例如 pickling/un-pickling 问题)。

解决方案 13:

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

如果您安装了IPython或Jupyter,则可以使用函数深度重新加载所有库:

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

如果你没有 Jupyter,请在 shell 中使用此命令进行安装:

pip3 install jupyter

解决方案 14:

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

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

解决方案 15:

Enthought Traits 有一个模块可以很好地实现这一点。https ://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html

它将重新加载任何已更改的模块,并更新使用它的其他模块和实例对象。它大多数时候无法处理__very_private__方法,并且可能会因类继承而阻塞,但它为我节省了大量时间,让我在编写 PyQt gui 或在 Maya 或 Nuke 等程序内运行的东西时不必重新启动主机应用程序。它可能有 20-30% 的时间不起作用,但它仍然非常有用。

Enthought 的软件包不会在文件更改时重新加载文件 - 你必须明确调用它 - 但如果你真的需要它,那应该不难实现

解决方案 16:

2018-02-01

  1. 模块foo必须提前成功导入。

  2. from importlib import reloadreload(foo)

31.5. importlib — import 的实现 — Python 3.6.4 文档

解决方案 17:

如果您遇到以下错误,此答案可能会帮助您找到解决方案:

回溯(最近一次调用最后一次):
 文件“FFFF”,第 1 行,位于  
NameError:名称“YYYY”未定义

或者

回溯(最近一次调用最后一次):
 文件“FFFF”,第 1 行,位于
 文件“/usr/local/lib/python3.7/importlib/__init__.py”,第 140 行,正在重新加载
   引发 TypeError(“reload() 参数必须是一个模块”)
TypeError:reload()参数必须是一个模块

如果您有类似下面的导入,您可能需要使用sys.modules来获取您想要重新加载的模块:

  import importlib
  import sys

  from YYYY.XXX.ZZZ import CCCC
  import AAA.BBB.CC


  def reload(full_name)
    if full_name in sys.modules:
      importlib.reload(sys.modules[full_name])


  reload('YYYY.XXX.ZZZ') # this is fine in both cases
  reload('AAA.BBB.CC')  

  importlib.reload(YYYY.XXX.ZZZ) # in my case: this fails
  importlib.reload(AAA.BBB.CC)   #             and this is ok

主要问题是importlib.reload接受模块仅仅接受字符串。

解决方案 18:

对我来说,对于 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'])

解决方案 19:

Python 不会重新计算子模块地址reload,即使它处于sys.modules

这是一个解决方法,虽然不完美,但有效。

# Created by BaiJiFeiLong@gmail.com at 2022/2/19 18:50
import importlib
import types

import urllib.parse
import urllib.request


def reloadModuleWithChildren(mod):
    mod = importlib.reload(mod)
    for k, v in mod.__dict__.items():
        if isinstance(v, types.ModuleType):
            setattr(mod, k, importlib.import_module(v.__name__))


fakeParse = types.ModuleType("urllib.parse")
realParse = urllib.parse

urllib.parse = fakeParse
assert urllib.parse is fakeParse

importlib.reload(urllib)
assert urllib.parse is fakeParse
assert getattr(urllib, "parse") is fakeParse

reloadModuleWithChildren(urllib)
assert urllib.parse is not fakeParse
assert urllib.parse is realParse

解决方案 20:

从 sys.modules 中删除模块也需要删除“无”类型。

方法 1:

import sys
import json  ##  your module

for mod in [ m for m in sys.modules if m.lstrip('_').startswith('json') or sys.modules[m] == None ]: del sys.modules[mod]

print( json.dumps( [1] ) )  ##  test if functionality has been removed

方法 2,使用簿记条目,删除所有依赖项:

import sys

before_import = [mod for mod in sys.modules]
import json  ##  your module
after_import = [mod for mod in sys.modules if mod not in before_import]

for mod in [m for m in sys.modules if m in after_import or sys.modules[m] == None]: del sys.modules[mod]

print( json.dumps( [2] ) )  ##  test if functionality has been removed

可选,只是为了确保所有条目都已输出,如果您愿意的话:

import gc
gc.collect()

解决方案 21:

我在用 Python 3.x 给学生项目评分时遇到了一个相关问题。我循环给学生代码评分,每个代码都在 main.py 中。不幸的是,import main不会加载下一个文件,因为 Python 已经看到一个名为的包main已被导入,所以它只是重用了那个包。此外,importlib.reload(main)也不起作用;如果学生没有定义一个函数,那么它只会使用前一个学生的 main.py 中的函数。(我相信这是因为它不会从旧包中删除新包中尚不存在的成员。)

因此我编写了一个函数来抽取包:

def kill_module(module):
    '''Kills all functions and classes in a module.'''
    import inspect
    if inspect.ismodule(module):
        elements = inspect.getmembers(module)
        for element_pair in elements:
            element_name = element_pair[0]
            element = element_pair[1]
            if inspect.isfunction(element) or inspec.isclass(element):
                delattr(main, element_name)

然后,在我迭代学生文件的循环中,我使用这个代码块强制从 main.py 卸载并重新加载主模块:

import importlib #this line is actually much higher in the file
main = importlib.import_module("main") #get a reference to the module
kill_module(main)
main = importlib.import_module("main")
importlib.reload(main) #force the reload from the file

注意:我必须这么做,因为我的学生都使用相同的文件名。目前我因为其他原因只能这么做。

解决方案 22:

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

下面允许您从名称上带有空格的路径重新加载模块,然后在重新加载后您可以像平常一样导入。

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

" )
    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解释器)。

解决方案 23:

另一种方法是将模块导入到函数中。这样,当函数完成时,模块就会被垃圾回收。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用