シェルから直接pythonのスクリプトを実行した時と、Ipython上でスクリプトを実行した際に、モジュールの読み込み挙動が異なりしばらくハマったため、その違いをまとめ共有します。
結論
思っていたよりも長い記事となってしまったため、先に結論を書いておきます。
pythonのデフォルトでは 実行するスクリプトのあるフォルダ でのモジュールの探索は行われるが、シェルのカレントディレクトリでの探索は行わない。
Ipythonでは Ipythonの機能として Ipythonのカレントディレクトリでのモジュール探索も行われる。
前提
普段、私は以下のように libs/ フォルダ以下に自作のモジュールやパッケージを置き、 scripts/ フォルダ以下にスクリプトを置いています。
project_root
|-- libs
| |-- my_module_01
| `-- my_module_02
`-- scripts
|-- script_01
`-- script_02
そして、 project_root/ でIpythonを起動し解析を行っています。スクリプトの実行もIpythonの中で run scripts/script_01 のようにして実行しています。
ハマった所
Ipythonでは問題なく実行できるスクリプトを、シェルから直接pythonを実行するとエラーが出ました。 以下のような簡単なモジュールとスクリプトを例として説明します。
# prpject_root/libs/my_module.py
def hello():
print('hello!')
# project_root/scripts/call_module.py
from libs.my_module import hello
if __name__ == '__main__':
hello()
スクリプト call_module.py は、 libs/ フォルダ下にある my_module.py から hello という関数を読み込んでいます。
このスクリプトは project_root で起動したIpython上では問題なく実行できますが、同じく project_root をカレントディレクトリとしたシェル上ではエラーが出ます。
# Ipython上での実行 pwd >>> .../project_root # カレントディレクトリの確認 run scripts/call_module.py >>> hello! # bash上での実行 pwd >>> .../project_root # カレントディレクトリの確認 python scripts/call_module.py >>> Traceback (most recent call last): >>> File "scripts/call_module.py", line 1, in >>> from libs.my_module import hello >>> ModuleNotFoundError: No module named 'libs'
上記のように、シェル上で call_module.py を実行しようとすると、 libs が見つからないというエラーが出ます。
問題点
libs が見つからないということは、シェル上で起動すると project_root/libs/ へのpathが通っていないということに成ります。しかし、この時私はPythonはカレントディレクトリもモジュールの探索に使用するはずと思っていたためになぜエラーが出たのかが分かりませんでした。
そこで、次のカレントディレクトリとpathを確認するためのスクリプトを scripts/ フォルダ下に置き、確認を行いました。
# project_root/scripts/check_path.py
import os
import sys
if __name__ == '__main__':
print('pwd:', os.getcwd(), '\n')
for place in sys.path:
print('path:', place)
このスクリプトを実行すると以下の結果が得られます。
# Ipython上での実行 run scripts/check_path.py >>> pwd: .../project_root >>> >>> path: >>> path: .../project_root/scripts >>> # 以下、個別に設定したパス # bash上での実行 python scripts/check_path.py >>> pwd: .../project_root >>> >>> path: .../scripts >>> # 以下、個別に設定したパス
Ipython上で実行した際に空白のpathがありますが、これはIpythonのカレントディレクトリ、すなわち .../project_root を意味します。一方、シェル上で実行した際にはカレントディレクトリにpathは通っていません。
また、両者ともに実行したスクリプトの置いてある project_root/scripts へのpathは通っています。
つまり、pythonのデフォルトでは 実行するスクリプトのあるフォルダ でのモジュールの探索は行いますが、シェルのカレントディレクトリでの探索は行わないようです。 そして、Ipythonでは Ipythonの機能として Ipythonのカレントディレクトリでのモジュール探索が行われるようです。
私は、このIpythonでの機能をpythonのデフォルトだと勘違いしていたためハマることと成りました。
以上より、Ipythonおよびシェルではモジュールの読み込みの挙動が異なるため、自作のモジュールを使用するスクリプトをIpython・シェルの両方で実行する可能性がある場合は、素直にモジュールのあるフォルダへのpathを事前に登録すべきのようです。