簡體   English   中英

如何在 Python 中重新加載模塊的功能?

[英]How to reload a module's function in Python?

跟進有關重新加載模塊的問題,如何從已更改的模塊重新加載特定功能?

偽代碼:

from foo import bar

if foo.py has changed:
    reload bar

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

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

在 python 2.7、3.5、3.6 上測試。

你想要的是可能的,但需要重新加載兩件事......首先是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 開始,需要先導入 reload 函數,通過from importlib import reload

熱重載不是你可以在 Python 中可靠地做的事情而不會炸毀你的頭。 如果不以特殊方式編寫代碼,您實際上無法支持重新加載,並且嘗試編寫和維護支持以任何理智的方式重新加載的代碼需要極端的紀律並且太混亂而值得付出努力。 測試這樣的代碼也不是一件容易的事。

解決方案是在代碼發生更改時完全重新啟動 Python 進程。 可以無縫地做到這一點,但具體如何取決於您的特定問題域。

雖然重新加載功能不是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  # 
>>> 

您將不得不使用reload加載模塊,因為您不能只重新加載函數:

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

您無法從模塊重新加載方法,但可以使用新名稱再次加載模塊,例如foo2並說bar = foo2.bar以覆蓋當前引用。

請注意,如果barfoo其他東西有任何依賴或任何其他副作用,您就會遇到麻煩。 所以雖然它有效,但它只適用於最簡單的情況。

如果你是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 位於不同模塊中的情況時,這種方法特別好。

這個也有效。

  • 優點:它支持 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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM