Edited at

Reloadしたモジュールについての注意点

More than 1 year has passed since last update.

インポートしたモジュールのソースを変更して、その変更を実行しているセッションに反映したいとき、モジュールを importlib.reload() (Python3) でリロード出来ますが、


  • リロードした前後のモジュールは同一オブジェクトですが、

  • リロードした前後のモジュールで定義されていた関数は別オブジェクト

  • リロードする前の関数が実行されるグローバル変数は更新後

となる点に注意が必要です。以下、サンプル例です。

更新前が次のコードだったとします。


reloadtest.py


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

次に、先ほどのモジュールを更新して、下記の変更をくわえます。


reloadtest.py


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__foobazは、ともに更新前の古い関数ですが、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)