我有一个长期运行的 Python 服务器,并且希望能够在不重新启动服务器的情况下升级服务。这样做的最佳方法是什么?
if foo.py has changed:
unimport foo <-- How do I do this?
import foo
myfoo = foo.Foo()
您可以使用 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 中,它是 moved 到 imp
模块。在 3.4 中,imp
是 deprecated 支持 importlib
。定位 3 或更高版本时,在调用 reload
时引用相应的模块或导入它。
我认为这就是你想要的。像 Django 的开发服务器这样的 Web 服务器使用它,这样您就可以看到代码更改的效果,而无需重新启动服务器进程本身。
引用文档:
重新编译 Python 模块的代码并重新执行模块级代码,通过重用最初加载模块的加载器,定义一组新的对象,这些对象绑定到模块字典中的名称。扩展模块的init函数不会被第二次调用。与 Python 中的所有其他对象一样,旧对象仅在其引用计数降至零后才会被回收。模块命名空间中的名称被更新以指向任何新的或更改的对象。对旧对象的其他引用(例如模块外部的名称)不会重新引用以引用新对象,并且如果需要,必须在它们出现的每个命名空间中进行更新。
正如您在问题中所指出的,如果 Foo
类驻留在 foo
模块中,您将不得不重构 Foo
对象。
在 Python 3.0–3.3 中,您将使用:imp.reload(module)
但是,imp
was deprecated in 3.4, in favour of importlib
(感谢 @Stefan!)。
我认为,因此,您现在应该使用 importlib.reload(module)
,尽管我不确定。
reload(__builtins__)
在 2.x 中同样有效
如果模块不是纯 Python,则删除模块可能特别困难。
以下是来自:How do I really delete an imported module?的一些信息
您可以使用 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() 函数的工件。
setattr(package, "empty", None)
reload()
仅重新加载最顶层的模块,除非您先从 sys.modules 中删除它,否则不会重新加载其中的任何内容。
reload(module)
,但前提是它完全独立。如果其他任何东西都引用了该模块(或属于该模块的任何对象),那么您将得到由旧代码比您预期的更长的时间引起的微妙和奇怪的错误,并且像 isinstance
这样的事情在不同的环境中不起作用相同代码的版本。
如果你有单向依赖,你还必须重新加载依赖于重新加载模块的所有模块,以摆脱对旧代码的所有引用。然后递归地重新加载依赖于重新加载的模块的模块。
如果您有循环依赖,这很常见,例如在处理重新加载包时,您必须一次性卸载组中的所有模块。您不能使用 reload()
执行此操作,因为它会在刷新其依赖项之前重新导入每个模块,从而允许旧引用潜入新模块。
在这种情况下,唯一的方法是破解 sys.modules
,这是不受支持的。您必须检查并删除您希望在下次导入时重新加载的每个 sys.modules
条目,并删除值为 None
的条目以处理与缓存失败的相对导入有关的实现问题。这不是非常好,但只要你有一组完全独立的依赖项,不会在其代码库之外留下引用,它是可行的。
最好重新启动服务器。 :-)
None
值的部分,因为我正好遇到了这个问题:我正在从 sys.modules
中删除项目,并且在重新导入后,一些导入的依赖项是 None
。
reload
函数吗?它是内置的,您不必导入任何库。
对于 Python 2,请使用内置函数 reload
:
reload(module)
对于 Python 2 和 Python 3.2—3.3 使用 reload
from module imp:
import imp
imp.reload(module)
对于 Python ≥3.4,imp
is deprecated 支持 importlib
,所以使用这个:
import importlib
importlib.reload(module)
或者:
from importlib import reload
reload(module)
TL;博士:
Python ≥ 3.4:importlib.reload(module)
Python 3.2 — 3.3:imp.reload(module)
Python 2:reload(module)
from six import reload_module
(当然首先需要 pip install six
)
from six.moves import reload_module
(doc)
if 'myModule' in sys.modules:
del sys.modules["myModule"]
nose.run()
时旧名称仍然存在,即使在 reload(my_module)
%run my_module
之后
[del(sys.modules[mod] for mod in sys.modules.keys() if mod.startswith('myModule.')]
之类的东西。
import sys; import json; del sys.modules['json']; print(json.dumps([1]))
和 json 模块仍在工作,即使它不再位于 sys.modules 中。
for mod in [ m for m in sys.modules if m.lstrip('_').startswith('json') ]: del sys.modules[mod]
before = [mod for mod in sys.modules] ; import json ; after = [mod for mod in sys.modules if mod not in before] ; for mod in [ m for m in sys.modules if m in after ]: del sys.modules[mod]
(代码块没有保留换行符。;表示换行符)
以下代码允许您兼容 Python 2/3:
try:
reload
except NameError:
# Python 3
from imp import reload
您可以在两个版本中将其用作 reload()
,这使事情变得更简单。
接受的答案不处理 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")
在重新加载的情况下,我们将顶级名称重新分配给存储在新重新加载的模块中的值,从而更新它们。
>>> from X import Y
之后重新加载执行 >>> __import__('X', fromlist='Y')
fromlist='*'
时让它工作?
from
。只需在代码中明确 import <package>
和显式 package.symbol 即可。意识到这可能并不总是可行或可取的。 (这里有一个例外:从未来导入 print_function。)
foo = reload(foo); from foo import *
如果您不在服务器中,但正在开发并需要经常重新加载模块,这里有一个很好的提示。
首先,确保您使用的是 Jupyter Notebook 项目中出色的 IPython shell。安装 Jupyter 后,您可以使用 ipython
或 jupyter console
,甚至更好的是 jupyter qtconsole
来启动它,这将为您提供一个漂亮的彩色控制台,并在任何操作系统中完成代码完成。
现在在你的 shell 中,输入:
%load_ext autoreload
%autoreload 2
现在,每次运行脚本时,都会重新加载模块。
除了 2
,还有其他 options of the autoreload magic:
%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。
这是重新加载模块的现代方式:
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
方法,可以使用模块调用该方法以重新加载它。例如,reload(math)
将重新加载 math
模块。
from importlib import reload
。然后你可以做reload(MODULE_NAME)
。不需要此功能。
modulereload(MODULE_NAME)
比 reload(MODULE_NAME)
更易于解释,并且与其他函数发生冲突的可能性更低。
对于像我这样想要卸载所有模块的人(在 Emacs 下的 Python 解释器中运行时):
for mod in sys.modules.values():
reload(mod)
Reloading Python modules 中有更多信息。
sys.modules.values()
中的所有内容都是模块。例如:>>> type(sys.modules.values()[1]) <class 'email.LazyImporter'>因此,如果我尝试运行该代码,它就会失败(我知道它并不是一个实用的解决方案,只是指出这一点)。
if mod and mod.__name__ != "__main__": imp.reload(mod)
编辑(答案 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)
原始答案
正如@bobince 的回答中所写,如果在另一个模块中已经引用了该模块(特别是如果它是使用 import numpy as np
等 as
关键字导入的),则该实例不会被覆盖。
在应用需要配置模块的“清白”状态的测试时,这对我来说非常有问题,因此我编写了一个名为 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) \
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 中的内部问题(例如酸洗/非酸洗问题)。
Enthought Traits 有一个模块可以很好地解决这个问题。 https://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html
它将重新加载任何已更改的模块,并更新正在使用它的其他模块和实例化对象。它在大多数情况下不适用于 __very_private__
方法,并且可能会阻塞类继承,但它为我节省了大量时间,因为在编写 PyQt guis 或在诸如 Maya 之类的程序中运行的东西时必须重新启动主机应用程序或核弹。它可能在 20-30% 的情况下不起作用,但它仍然非常有用。
Enthought 的包不会在文件更改的那一刻重新加载文件——你必须明确地调用它——但如果你真的需要它,这应该不难实现
其他选项。看到 Python 默认 importlib.reload
只会重新导入作为参数传递的库。它不会重新加载您的 lib 导入的库。如果您更改了很多文件并且要导入一个有些复杂的包,则必须执行深度重新加载。
如果您安装了 IPython 或 Jupyter,您可以使用函数来深度重新加载所有库:
from IPython.lib.deepreload import reload as dreload
dreload(foo)
如果您没有 Jupyter,请在 shell 中使用以下命令安装它:
pip3 install jupyter
reload() argument must be module
。我正在使用自定义函数导入,但似乎不起作用。使用内置模块确实有效。 :-( 对于我对代码所做的每一个小改动都重新加载 iPython 是浪费时间......
那些使用 python 3 并从 importlib 重新加载的人。
如果您遇到模块似乎没有重新加载的问题......那是因为它需要一些时间来重新编译 pyc(最多 60 秒)。我写这个提示只是为了让您知道您是否遇到过这种问题。
2018-02-01
模块 foo 必须提前成功导入。从 importlib 导入重新加载,重新加载(foo)
31.5. importlib — The implementation of import — Python 3.6.4 documentation
对我来说,就 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'])
如果您遇到以下错误,此答案可能会帮助您获得解决方案:
Traceback(最近一次调用最后一次):文件“FFFF”,第 1 行,在 NameError:名称 'YYYY' 未定义
或者
回溯(最后一次调用):文件“FFFF”,第 1 行,在文件“/usr/local/lib/python3.7/importlib/__init__.py”中,第 140 行,在重新加载中引发 TypeError("reload() 参数必须是模块”)类型错误: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
只接受模块而不接受字符串。
从 sys.modules 中删除模块也需要删除“无”类型。
方法一:
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()
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
另一种方法是在函数中导入模块。这样,当函数完成时,模块就会被垃圾收集。
sys.modules
中保存了一个全局引用。
我在尝试在 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( "\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
解释器)。
is_changed
函数只是您必须编写的任意函数;它不是内置的。例如,它可能会打开与您正在导入的模块相对应的文件,并将其与缓存版本进行比较,以查看它是否已更改。