インポートしたモジュールのソースを変更して、その変更を実行しているセッションに反映したいとき、モジュールを importlib.reload()
(Python3) でリロード出来ますが、
- リロードした前後のモジュールは同一オブジェクトですが、
- リロードした前後のモジュールで定義されていた関数は別オブジェクト
- リロードする前の関数が実行されるグローバル変数は更新後
となる点に注意が必要です。以下、サンプル例です。
更新前が次のコードだったとします。
def foo(n):
"""Return 0 for all n >= 0"""
if n == 0:
return n
else:
return foo(n - 1)
def baz(n):
"""Return True"""
return True
インタラクティブセッションで、次のようにfoo
を調べておきます。
>>> import reloadtest
>>> dir(reloadtest)
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'baz',
'foo']
>>> foo = reloadtest.foo
>>> baz = reloadtest.baz
>>> foo(0)
0
>>> foo(2)
0
次に、先ほどのモジュールを更新して、下記の変更をくわえます。
def foo(n):
"""Return 1 for all n >= 1"""
if n == 1:
return n
else:
return foo(n - 1)
def bar(n):
"""Return 2 for all n >= 0"""
if n == 2:
return n
else:
return foo(n)
次からは、モジュールのリロードとその後の挙動を調べたセッションです。
更新前後のモジュールオブジェクトは同一であることが次から分かります。
>>> from importlib import reload
>>> newmodule = reload(reloadtest)
>>> newmodule is reloadtest
True
reloadtest
モジュールのグローバルには、更新後のソースにはないbaz
が残っていることが分かります。
>>> dir(reloadtest)
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'bar',
'baz',
'foo']
更新前後のfoo
は、異なる関数オブジェクトであることが分かります。
>>> foo is reloadtest.foo
False
最後のポイントですが、古いfoo
を実行すると、古いコードオブジェクトが実行されていますが、
foo(2)
は、新しい関数の結果です。つまり、古い関数の中のfoo
は、新しいfoo
を指していることがわかります。
>>> foo(1)
0
>>> foo(2)
1
特にinspect
ライブラリのを用いてgetsource
など、オブジェクトのソースを取得する関数は、オブジェクト自体にはソースは保存されておらず、呼ばれた時点で保存されているファイル名と行番号を頼りにソースファイルを見に行くためリロード前に定義されていた古いオブジェクトに使用すると、更新後ソースを見に行き、オブジェクトに保存されているcode objectとは対応しなくなります。
__main__
のfoo
、baz
は、ともに更新前の古い関数ですが、foo
は更新後のソース
baz
は、更新前にあった場所に定義された別の関数bar
をとってきてしまっています。
>>> import inspect
>>> print(inspect.getsource(foo))
def foo(n):
"""Return 1 for all n >= 0"""
if n == 1:
return n
else:
return foo(n - 1)
>>> print(inspect.getsource(baz))
def bar(n):
"""Return 2 for all n >= 0"""
if n == 2:
return n
else:
return foo(n)