【发布时间】:2011-11-08 10:08:46
【问题描述】:
跟进this question regarding reloading a module,如何从更改的模块中重新加载特定功能?
伪代码:
from foo import bar
if foo.py has changed:
reload bar
【问题讨论】:
标签: python methods import reload
跟进this question regarding reloading a module,如何从更改的模块中重新加载特定功能?
伪代码:
from foo import bar
if foo.py has changed:
reload bar
【问题讨论】:
标签: python methods import reload
截至今天,正确的做法是:
import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar
在 python 2.7、3.5、3.6 上测试。
【讨论】:
importlib.reload(sys.modules.get('foo', sys))
AttributeError: 'module' object has no attribute 'reload'
你想要的是可能的,但需要重新加载两件事......首先是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 函数
【讨论】:
热重载不是您可以在 Python 中可靠地执行而不会让您头疼的事情。如果不以特殊方式编写代码,您实际上无法支持重新加载,并且尝试编写和维护支持重新加载的任何理智的代码需要严格的纪律,并且过于混乱,不值得付出努力。测试这样的代码也不是一件容易的事。
解决方案是在代码发生更改时完全重新启动 Python 进程。可以无缝地执行此操作,但具体如何取决于您的特定问题域。
【讨论】:
reload 停靠点,基本上都说同样的话。
importlib.reload() 工作正常,非常适合开发人员。只需要知道它是如何工作的,没有任何问题。
虽然重新加载函数不是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 #
>>>
【讨论】:
这个也可以。
# 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()
【讨论】:
您必须使用reload 重新加载模块,因为您不能只重新加载函数:
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>
【讨论】:
reload(foo),bar 将不会改变。 会 发生的事情微妙而令人困惑,因此最好不要使用reload 或尝试在 Python 中热重新加载代码。你的理智和可靠性会感谢你。
exit,它是否会更改from sys import exit,然后更改sys.exit,然后更改import sys 和reload(sys)?
sys 的示例中,这甚至是不可能的。)
from ... import ...!
reload 也没有有用的建议。
您无法从模块中重新加载方法,但可以使用新名称再次加载模块,例如 foo2 和 bar = foo2.bar 以覆盖当前引用。
请注意,如果bar 对foo 中的其他东西有任何依赖或任何其他副作用,您就会遇到麻烦。因此,虽然它有效,但它只适用于最简单的情况。
【讨论】:
如果你是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 的情况下它位于不同的模块中。
【讨论】: