如何在 Python 中重新加载模块的函数?

Posted

技术标签:

【中文标题】如何在 Python 中重新加载模块的函数?【英文标题】:How to reload a module's function in Python? 【发布时间】:2011-11-08 10:08:46 【问题描述】:

跟进this question regarding reloading a module,如何从更改的模块中重新加载特定功能?

伪代码:

from foo import bar

if foo.py has changed:
    reload bar

【问题讨论】:

【参考方案1】:

截至今天,正确的做法是:

import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar

在 python 2.7、3.5、3.6 上测试。

【讨论】:

这个答案现在应该被标记为接受的答案,因为虽然最初接受的答案在理论上是正确的,但这个答案是实际的答案并且支持更新版本的 python @Antony Hatchkins 如果 foo 之前没有加载,则会引发错误。您介意编辑和使用以下内容吗? importlib.reload(sys.modules.get('foo', sys)) @mbh86 是的,它在某些情况下可能有用,但我宁愿保留它目前的样子,因为它更短,并且对于寻求快速答案的读者来说是立即清晰的。跨度> 对我不起作用,出现错误:AttributeError: 'module' object has no attribute 'reload'【参考方案2】:

你想要的是可能的,但需要重新加载两件事......首先是reload(foo),然后你还必须reload(baz)(假设baz是包含from foo import bar语句的模块的名称)。

至于为什么...当foo首次加载时,会创建一个foo对象,其中包含一个bar对象。当您将bar 导入baz 模块时,它会存储对bar 的引用。当调用reload(foo) 时,foo 对象被置空,并且模块重新执行。这意味着所有foo 引用仍然有效,但是已经创建了一个新的bar 对象......所以在某处导入的所有引用仍然是对 bar 对象的引用。通过重新加载baz,您会导致它重新导入新的bar


或者,您可以在您的模块中执行import foo,并始终调用foo.bar()。这样,无论何时您reload(foo),您都会获得最新的bar 参考。

注意:从 Python 3 开始,需要先通过 from importlib import reload 导入 reload 函数

【讨论】:

问题是它会抱怨没有 foo,因为它从来没有被直接导入。 需要先导入foo,然后重新加载,再重新导入bar才行 @alexvicegrab 实际上不需要导入 foo。请参阅下面的answer。【参考方案3】:

热重载不是您可以在 Python 中可靠地执行而不会让您头疼的事情。如果不以特殊方式编写代码,您实际上无法支持重新加载,并且尝试编写和维护支持重新加载的任何理智的代码需要严格的纪律,并且过于混乱,不值得付出努力。测试这样的代码也不是一件容易的事。

解决方案是在代码发生更改时完全重新启动 Python 进程。可以无缝地执行此操作,但具体如何取决于您的特定问题域。

【讨论】:

所有其他相关问题,以及reload 停靠点,基本上都说同样的话。 不确定我是否同意。 importlib.reload() 工作正常,非常适合开发人员。只需要知道它是如何工作的,没有任何问题。【参考方案4】:

虽然重新加载函数不是reload 函数的特性,但它仍然是可能的。我不建议在生产中这样做,但它是这样工作的: 您要替换的函数是内存中某处的对象,并且您的代码可能包含对它的许多引用(而不是对函数名称的引用)。但是该函数在调用时实际执行的操作并没有保存在该对象中,而是保存在另一个对象中,而该对象又被该函数对象引用,在其属性__code__ 中。所以只要有对函数的引用,就可以更新它的代码:

模块 mymod:

from __future__ import print_function
def foo():
    print("foo")

其他模块/python会话:

>>> import mymod
>>> mymod.foo()
foo
>>> old_foo_ref = mymod.foo
>>> # edit mymod.py to make function "foo" print "bar"
>>> reload(mymod)
<module 'mymod' from 'mymod.py'>
>>> old_foo_ref()   # old reference is running old code
foo
>>> mymod.foo()     # reloaded module has new code
bar
>>> old_foo_ref.__code__ = mymod.foo.__code__
>>> old_foo_ref()   # now the old function object is also running the new code
bar
>>> 

现在,如果您没有对旧函数的引用(即 lambda 传递给另一个函数),您可以尝试使用 gc 模块通过搜索列表来获取它所有对象:

>>> def _apply(f, x):
...     return lambda: f(x)
... 
>>> tmp = _apply(lambda x: x+1, 1)  # now there is a lambda we don't have a reference to
>>> tmp()
2
>>> import gc
>>> lambdas = [obj for obj in gc.get_objects() if getattr(obj,'__name__','') == '<lambda>'] # get all lambdas
[<function <lambda> at 0x7f315bf02f50>, <function <lambda> at 0x7f315bf12050>]     
# i guess the first one is the one i passed in, and the second one is the one returned by _apply
# lets make sure:
>>> lambdas[0].__code__.co_argcount
1  # so this is the "lambda x: ..."
>>> lambdas[1].__code__.co_argcount
0  # and this is the "lambda: ..."
>>> lambdas[0].__code__ = (lambda x: x+2).__code__  # change lambda to return arg + 2
>>> tmp()
3  # 
>>> 

【讨论】:

【参考方案5】:

这个也可以。

优点:它支持 from ... import ... 而不是 module.my_function,并且不需要将模块写为字符串。 缺点:需要导入模块。
# Import of my_function and module
import my_utils
from my_utils import my_function

# Test:
my_function()

# For reloading:
from importlib import reload
reload(my_utils)
from my_utils import my_function

# Test again:
my_function()

【讨论】:

【参考方案6】:

您必须使用reload 重新加载模块,因为您不能只重新加载函数:

>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>

【讨论】:

如果在该代码中 OP 确实 reload(foo)bar不会改变。 发生的事情微妙而令人困惑,因此最好不要使用reload 或尝试在 Python 中热重新加载代码。你的理智和可靠性会感谢你。 你测试过这个吗?如果您更改exit,它是否会更改from sys import exit,然后更改sys.exit,然后更改import sysreload(sys) (请注意,在内置 sys 的示例中,这甚至是不可能的。) 但我没说from ... import ... @rm,OP 做了!即使他没有,reload没有有用的建议。【参考方案7】:

您无法从模块中重新加载方法,但可以使用新名称再次加载模块,例如 foo2bar = foo2.bar 以覆盖当前引用。

请注意,如果barfoo 中的其他东西有任何依赖或任何其他副作用,您就会遇到麻烦。因此,虽然它有效,但它只适用于最简单的情况。

【讨论】:

【参考方案8】:

如果你是foo.py的作者,你可以写:

with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec')

def bar(a,b):
    exec(bar_code)

def reload_bar():
    global bar_code
    with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec') 

然后,在您的伪代码中,如果 bar.py 已更改,则重新加载。当 bar 与想要热重载它的代码位于同一个模块中时,这种方法特别好,而不是 OP 的情况下它位于不同的模块中。

【讨论】:

以上是关于如何在 Python 中重新加载模块的函数?的主要内容,如果未能解决你的问题,请参考以下文章

python如何重新加载模块

如何在烧瓶中重新加载python模块?

脚本编译后如何在python中重新加载模块?

如何使 VSCode 自动重新加载外部 *.py 模块?

如何重新加载 Python3 C 扩展模块?

在 Python 中,如何在重新加载后更改实例化对象?