如何卸载(重新加载)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()
解决方案 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.4,imp
已弃用而改为使用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
,该函数使用importlib
的reload
函数并递归覆盖所有声明的模块的属性。它已使用 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
模块
foo
必须提前成功导入。from importlib import reload
,reload(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:
另一种方法是将模块导入到函数中。这样,当函数完成时,模块就会被垃圾回收。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件