web-dev-qa-db-ja.com

Pythonでモジュールの関数をリロードする方法は?

モジュールのリロードに関するこの質問 に続き、変更されたモジュールから特定の関数をリロードするにはどうすればよいですか?

擬似コード:

from foo import bar

if foo.py has changed:
    reload bar
37
Jonathan

あなたが望むものは可能ですが、2つのものをリロードする必要があります...最初にreload(foo)が必要ですが、次にreload(baz)bazがモジュールの名前であると仮定) _from foo import bar_ステートメントを含む)。

理由について... fooが最初にロードされると、fooオブジェクトを含むbarオブジェクトが作成されます。 barbazモジュールにインポートすると、barへの参照が格納されます。 reload(foo)が呼び出されると、fooオブジェクトが空白になり、モジュールが再実行されます。つまり、すべてのfoo参照は引き続き有効ですが、新しいbarオブジェクトが作成されています...したがって、どこかにインポートされたすべての参照は、引き続きoldへの参照です= barオブジェクト。 bazをリロードすると、新しいbarが再インポートされます。


または、モジュールで_import foo_を実行し、常にfoo.bar()を呼び出すこともできます。そうすれば、reload(foo)を実行するたびに、最新のbar参照を取得できます。

注:Python 3の時点で、再ロード関数は_from importlib import reload_を介して最初にインポートする必要があります

10
Eli Collins

今日、これを行う適切な方法は次のとおりです。

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

python 2.7、3.5、3.6でテスト済み。

36

ホットリロードは、Pythonで確実にできることではありません。頭を爆破することなく確実に実行できます。文字どおりリロードをサポートするには、特別な方法でコードを記述せず、正気でリロードをサポートするコードを記述して維持する必要があります。このようなコードのテストも簡単な作業ではありません。

解決策は、コードが変更されたときにPythonプロセスを完全に再起動することです。これをシームレスに実行することは可能ですが、特定の問題ドメインによって異なります。

11
Mike Graham

関数の再読み込みは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  # 
>>> 
3
pixelbrei

モジュールからメソッドをリロードすることはできませんが、モジュールを新しい名前で再度ロードできます。たとえば、foo2 そして、言います bar = foo2.bar現在の参照を上書きします。

barfoo内の他のものに依存している場合、または他の副作用がある場合は、問題が発生することに注意してください。したがって、機能する一方で、最も単純なケースでのみ機能します。

2
Aaron Digulla

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が変更されている場合は、疑似コードでリロードします。このアプローチは、バーが別のモジュールに存在するOPの場合ではなく、コードをホットリロードしたいコードと同じモジュールに存在する場合に特に適しています。

1
Eponymous

関数だけを再ロードすることはできないため、モジュールを再ロードするにはreloadを使用する必要があります。

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