[Python] インタラクティブシェルでモジュールの検索パス追加とリロードをする方法

こんにちは。サービスグループの武田です。Pythonのインタラクティブシェル(REPL)でモジュールの検索パス追加とリロードをする方法を紹介します。
2021.07.31

こんにちは。サービスグループの武田です。

PythonはいわゆるREPL(Read-Eval-Print Loop)と呼ばれる、対話型のモードを持っています。これを使用することで電卓の代わりに計算をしたり、コードスニペットを実行したりできます。

Pythonはimport命令で他のファイルのコードを読み込むことができますが、これはインタラクティブシェルでも同様です。ところで、ディレクトリ構成と実際に動作させる環境が異なる場合、importで指定するモジュール名は同一にならない場合があります。

たとえば次のようなディレクトリ構成のプロジェクトがあったとします。

$ tree
.
├── Pipfile
├── Pipfile.lock
└── mod1
    └── lib.py

1 directory, 3 files

プロジェクトのルートディレクトリ配下にmod1があります。ところがデプロイ後はこのmod1がルートディレクトリになるとします。そうすると、lib.pyを読み込むにはimport libと書きますが、このプロジェクト構成では失敗します(import mod1.libなら成功します)。簡単に試してみます。

mod1/lib.py

def add(a, b):
    return a + b
>>> import lib
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'lib'

モジュール検索パスの確認と追加

Pythonはimportをする際に探す場所が設定されています。これはsys.pathで確認できますので見てみます。

>>> import sys
>>> print(sys.path)
['', '/usr/local/Cellar/python@3.9/3.9.1_5/Frameworks/Python.framework/Versions/3.9/lib/python39.zip', '/usr/local/Cellar/python@3.9/3.9.1_5/Frameworks/Python.framework/Versions/3.9/lib/python3.9', '/usr/local/Cellar/python@3.9/3.9.1_5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload', '/Users/example/Library/Python/3.9/lib/python/site-packages', '/usr/local/lib/python3.9/site-packages', '/usr/local/Cellar/protobuf/3.14.0/libexec/lib/python3.9/site-packages']

先頭に''があるためカレントディレクトリのファイルは問題なくimportできます。ところがlib.pymod1配下のため探し出すことができません。この検索パスに追加することで解決できます。

>>> sys.path.append('mod1')
>>> import lib
>>> lib.add(10, 20)
30

モジュールのリロード

もうひとつ知っておくと便利なTipsとして、モジュールのリロードがあります。開発中でソースコードを修正しながら動作確認をした場合、そのままだと意図した挙動とはなりません。テキストエディタなどを開きlib.pyを変更してみます。

mod1/lib.py

def add(a, b):
    return a - b

とりあえず何もせず関数を呼んでみます。

>>> lib.add(10, 20)
30

まぁこれは当然として、一度importし直してみます。

>>> import lib
>>> lib.add(10, 20)
30

それでも挙動は変わりません。Pythonは一度ロードしたモジュールを何度も読み込まないため、これだけではダメなのです。そこで必要なのがimportlib.reloadです。試してみましょう。

>>> import importlib
>>> importlib.reload(lib)
<module 'lib' from 'mod1/lib.py'>
>>> lib.add(10, 20)
-10

今度は修正後の挙動となりました。

まとめ

モジュールのリロードは、この方法を知るまでは一度シェルを閉じて再起動していました。時間がかかる操作ではありませんが、手間ではあったので、この方法が知れてよかったです。それでは、よいPythonライフを。