LoginSignup
0
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-18

インポートしたモジュールのソースを変更して、その変更を実行しているセッションに反映したいとき、モジュールを 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)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0