シェルから直接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を事前に登録すべきのようです。