概要
外部ライブラリや自作モジュールをインポートする際に探索するパスと、テキスト等のファイルを読込する際に探索するパスが違うことに気づかずハマったことがあるので、備忘のために解説します。
プロジェクトフォルダの構造(例)
project/
├ conf/
│ └ conf.json
└ src/
├ utils
│ ├ fileutils.py
│ └ remoteutils.py ← fileutils.py をインポート、conf.jsonを読込
└ func01/
├ syspath.py
├ filepath.py
└ main.py ← remoteutils.py をインポート
コードの内容
必要な説明は後述します。
{
"conf" : {
"content": "設定情報が読み込まれました。"
}
}
import json
def get_json_data(path: str):
# r:読み取り専用 -1:バッファリング無し, utf-8:文字コード
with open(path, "r", -1, "utf-8") as file:
file_data = file.read()
json_data = json.loads(file_data)
return json_data
import src.utils.fileutils as fileutils
conf = fileutils.get_json_data("conf/conf.json")
def remoteutils_call():
print(conf["conf"]["content"])
import src.utils.remoteutils as remoteutils
def main():
remoteutils.remoteutils_call()
if __name__ == "__main__":
main()
モジュールのインポート
モジュール探索パス(PYTHONPATH)
モジュール探索パスとは、外部ライブラリや自作モジュールをインポートする際の探索の起点となるパスのことです。import文を記載する時に意識すべきで、sysライブラリを利用して探索パスを確認できます。
モジュール探索パスの確認方法
import sys
import pprint
pprint.pprint(sys.path)
上記のスクリプトはどこのディレクトリで実行しても同じ結果になるため、
ターミナルのカレントディレクトリがprojectでも、prpject\src\func01でも結果は変わりません。PYTHONPATHに対して手を加えていなければ次のような結果になるはずです。
実行結果
[省略..\\project\\src\\func01\\, ← syspath.pyを格納したパス
省略..\\Python\\Python312\\python312.zip',
省略..\\Python\\Python312\\DLLs',
省略..\\Python\\Python312\\Lib',
省略..\\Python\\Python312',
省略..\\Python\\Python312\\Lib\\site-packages']
[省略..\\project\\src\\func01\\, ← syspath.pyを格納したパス
省略..\\Python\\Python312\\python312.zip',
省略..\\Python\\Python312\\DLLs',
省略..\\Python\\Python312\\Lib',
省略..\\Python\\Python312',
省略..\\Python\\Python312\\Lib\\site-packages'
省略..\\project', ← 仮想環境のルートパス
省略..\\project\\Lib\\site-packages'] ← 仮想環境の外部ライブラリインストール先
VSCodeのターミナルでsyspath.pyを実行すると、エディタの自動環境検出機能によって仮想環境をアクティブしていなくても仮想環境のモジュール探索パスが追加されていることがあります。
インポート文の実装
インポートではモジュール探索パスからの相対パスを指定します。
project/
├ conf/
│ └ conf.json
└ src/
├ utils
│ ├ fileutils.py
│ └ remoteutils.py ← fileutils.py をインポート
└ func01/
├ syspath.py
├ filepath.py
└ main.py ← remoteutils.py をインポート
冒頭でも説明した通り、main.pyではremoteutils.pyをインポート、
remoteutils.pyではfileutils.pyをインポートします。(インポート文以外省略)
import src.utils.remoteutils as remoteutils
import src.utils.fileutils as fileutils
remoteutils.pyのインポート文について
import fileutils
remoteutils.pyとfileutils.pyが同じフォルダに配置されているため、上記でも可能です。(スクリプトが保存されたパスもモジュール探索パスに含まれる)
ファイルの読み込み
ファイル探索パス
conf.jsonを読込する際はファイルパスを探索します。
ファイル探索パスはスクリプトを実行した際のカレントディレクトリが基準です。
よって、実行した時のディレクトリによって結果が異なります。
確認方法
import os
print(os.getcwd())
実行結果
PS 省略..\project>Python src\func01\filepath.py
省略..\project
PS 省略..\project\src\func01>Python filepath.py
省略..\project\src\func01
main.pyはprojectからの相対パス参照で実行することを前提として、remoteutils.pyの実装をします。(パス指定部分以外省略)
conf = fileutils.get_json_data("conf/conf.json")
ファイルパスの指定方法について
パスは"\"区切りでも大丈夫ですが、その場合は文字列先頭に"r"をつけるか、"\"をエスケープ("\\"と記載)することを推奨します。
# / 区切り指定
conf = fileutils.get_json_data("conf/conf.json")
# raw文字列指定
conf = fileutils.get_json_data(r"conf\conf.json")
# \ をエスケープ
conf = fileutils.get_json_data("conf\\conf.json")
# 非推奨(パスに"\n"や"\t"が含まれるとコケる)
conf = fileutils.get_json_data("conf\conf.json")
実行結果
PS ..省略\project>python src\func01\main.py
設定情報が読み込まれました。
実行ディレクトリに注意
PS ..省略\project\src\fl01\jd01> python .\main.py
Traceback (most recent call last):
File "..省略\project\src\fl01\jd01\main.py", line 1, in <module>
import src.utils.remoteutils as remoteutils
File "..省略\project\src\utils\remoteutils.py", line 3, in <module>
conf = fileutils.get_json_data("conf/conf.json")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "..省略\project\src\utils\fileutils.py", line 6, in get_json_data
with open(path, "r", -1, "utf-8") as file:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'conf/conf.json'
cdコマンド等で下記ディレクトリに移動してから実行するとエラーが発生します。
PS ..省略\project\src\fl01\jd01> python .\main.py
パス探索が下記のようになってしまうことが原因です。
..省略\project\src\fl01\jd01\conf\conf.json
まとめ
モジュール探索パス
- 外部ライブラリや、自作モジュールのインポートに利用される
- 起点となるパスは複数存在する
- 仮想環境のアクティブ化、非アクティブ化によってモジュール探索パスが変わる
- 任意にモジュール探索パスを追加することが可能
- モジュール探索パスからの相対参照のみ可能であり、絶対参照という概念は無い
ファイル探索パス
- テキストやExcelブックなどのファイルを参照する際に利用される
- 起点となるパスはスクリプト実行時のカレントディレクトリのみ
- 絶対パスの指定が可能