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

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

if foo.py has changed:unimport foo  <-- How do I do this?import foomyfoo = foo.Foo()
845045 次浏览

您可以使用#0重新加载已经导入的模块:

from importlib import reload  # Python 3.4+import foo
while True:# Do some things.if is_changed(foo):foo = reload(foo)

在Python 2中,#0是内置的。在Python 3中,它是移动#1模块。在3.4中,imp已弃用而不是#3。当针对3或更高版本时,在调用reload时引用适当的模块或导入它。

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

引用文档:

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

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

reload(module),但前提是它是完全独立的。如果其他任何东西都有对该模块(或属于该模块的任何对象)的引用,那么你会得到微妙而奇怪的错误,原因是旧代码停留的时间比你预期的要长,比如isinstance在同一代码的不同版本中不起作用。

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

如果你有循环依赖关系,这很常见,例如当你处理重装包时,你必须一次性卸载组中的所有模块。你不能用reload()这样做,因为它会在刷新依赖关系之前重新导入每个模块,允许旧引用悄悄进入新模块。

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

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

如果不是纯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()函数。

在Python 3.0-3.3中,您将使用:#0

BDFL回答这个问题。

#0在3.4中被弃用,支持#1(谢谢@陈志立!

认为,因此,你现在会使用#0,尽管我不确定。

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

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

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

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

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

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

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

接受的答案不处理from 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 parseMessagesimportOrReload("dfly_parser", "parseMessages")

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

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

对于Python2,使用内置函数#0

reload(module)

对于Python2 Python3.2-3.3使用#0模块imp

import impimp.reload(module)

对于Python≥3.4.4imp已弃用支持#1,所以使用这个:

import importlibimportlib.reload(module)

或:

from importlib import reloadreload(module)

太长别读:

Python≥3.4:importlib.reload(module)
Python 3.2-3.3:imp.reload(module)
Python 2:reload(module)

EnthingTraits有一个模块,在这方面工作得相当好。https://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html

它会重新加载任何已更改的模块,并更新其他模块和正在使用它的实例化对象。它大部分时间不适用于__very_private__方法,并且可能会窒息类继承,但它为我节省了大量的时间,因为我在编写PyQt guis或Maya或Nuke等程序内部运行的东西时不得不重新启动主机应用程序。它可能20%-30%的时间不起作用,但它仍然非常有用。

在文件发生变化的那一刻,En的软件包不会重新加载文件-你必须明确地称之为-但如果你真的需要它,这应该不难实现

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

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

下面接受您从路径中重新加载模块,其名称上带有空格,然后重新加载后,您可以像往常一样导入。

def reload_module(full_module_name):"""Assuming the folder `full_module_name` is a folder inside somefolder on the python sys.path, for example, sys.path as `C:/`, andyou are inside the folder `C:/Path With Spaces` on the file`C:/Path With Spaces/main.py` and want to re-import some files onthe folder `C:/Path With Spaces/tests`
@param full_module_name   the relative full path to the module fileyou want to reload from a folder on thepython `sys.path`"""import impimport sysimport 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_testsfrom .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解释器)。

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

from importlib import reload

如果您想支持3.5以上的Python版本,请使用:

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

这定义了一个reload方法,可以用模块调用该方法来重新加载它。例如,reload(math)将重新加载math模块。

2018-02-01

  1. 模块foo必须提前成功导入。
  2. from importlib import reloadreload(foo)

31.5

如果你是服务器中的没有,但开发并且需要经常重新加载模块,这里有一个很好的提示。

首先,确保您使用的是Jupyter Notebook项目中出色的ipython shell。安装Jupyter后,您可以从ipythonjupyter console开始,甚至更好,jupyter qtconsole,这将为您提供一个漂亮的彩色控制台,可以在任何操作系统中完成代码。

现在在你的shell中,键入:

%load_ext autoreload%autoreload 2

现在,每次你运行你的脚本,你的模块将被重新加载。

除了2之外,还有其他自动加载魔法的选项

%autoreloadReload all modules (except those excluded by %aimport) automatically now.
%autoreload 0Disable automatic reloading.
%autoreload 1Reload all modules imported with %aimport every time before executing the Python code typed.
%autoreload 2Reload all modules (except those excluded by %aimport) every time beforeexecuting the Python code typed.

当然,它也可以在Jupyter Notebook上使用。

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

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

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

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

pip3 install jupyter

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

如果你有这样的问题,模块似乎没有重新加载…那是因为它需要一些时间来重新编译pyc(长达60秒)。我写这个提示只是让你知道你是否遇到过这种问题。

编辑(答案V2)

之前的解决方案对于获取重置信息很好,但它不会更改所有引用(超过reload但不需要)。要实际设置所有引用,我必须进入垃圾收集器,并在那里重写引用。现在它工作得很好!

请注意,如果GC已关闭,或者重新加载不受GC监控的数据,则此不会有效。如果您不想弄乱GC,原始答案可能对您来说已经足够了。

新代码:

import importlibimport inspectimport gcfrom enum import EnumMetafrom 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 amodule, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classesto 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 packageif 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 GCreturn
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_attrsfor 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-resetif (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 importlibimport inspectfrom 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 amodule, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classesto 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 packageif 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 itemsetattr(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-resetif (mod_name is None) or (mod_name != module_name) or (id(sitem) in reset_items) \or isinstance(sitem, EnumMeta):  # Deal with enumscontinue
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中的内部问题(例如酸洗/取消酸洗问题)。

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

Traceback (most recent call last):File "FFFF", line 1, inNameError: name 'YYYY' is not defined

Traceback (most recent call last):File "FFFF", line 1, inFile "/usr/local/lib/python3.7/importlib/__init__.py", line 140, in reloadraise TypeError("reload() argument must be a module")TypeError: reload() argument must be a module

如果你有一个像下面这样的导入,你可能需要使用sys.modules来获取你想要重新加载的模块:

  import importlibimport sys
from YYYY.XXX.ZZZ import CCCCimport 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 casesreload('AAA.BBB.CC')
importlib.reload(YYYY.XXX.ZZZ) # in my case: this failsimportlib.reload(AAA.BBB.CC)   #             and this is ok

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

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

方法一:

import sysimport 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 moduleafter_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 gcgc.collect()

Python不会在reload重计算子模块寻址,如果它在sys.modules中则会发生事件

这是一个变通办法,不完美,但工作。

# Created by BaiJiFeiLong@gmail.com at 2022/2/19 18:50import importlibimport types
import urllib.parseimport 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 = fakeParseassert urllib.parse is fakeParse
importlib.reload(urllib)assert urllib.parse is fakeParseassert getattr(urllib, "parse") is fakeParse
reloadModuleWithChildren(urllib)assert urllib.parse is not fakeParseassert urllib.parse is realParse