👋

【Python入門】どこから実行してもOK!pathlibを使った設定ファイルの確実な読み込み

に公開

1. 目的

この記事では、open() で指定するファイルパスが、カレントディレクトリに依存しない絶対パスとするための方法について解説していきます。

2. 背景

2-1. 作成プログラム: 簡易版RPCプログラム

  • クライアント: Node.js
  • サーバー: Python
  • UNIXドメインソケットを使った通信
  • config.json にサーバーへのファイルパスを設定

2-2. ディレクトリ構造

ディレクトリ構成
05_remote_procedure_call/
├── src/
│   ├── client/
│   │   ├── client.js
│   │   └── method_table.js
│   └── server/
│       ├── functions.py
│       ├── method_table.py
│       └── server.py
├── tmp/
├── config.json
└── README.md

2-3. config.json の設定内容

{
    "rpc_server_socket_path" : "/tmp/server.sock"
}

3. 直面した課題

実行時のカレントディレクトリによって、ファイルが見つからない FileNotFoundError が発生していたこと

コード
...
import json
from method_table import method_table
from pathlib import Path

# 受付ソケットオブジェクト作成
rpc_server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

config = json.load(open('config.json'))

rpc_server_address = config['rpc_server_socket_path']
...

このコードで、config.json ファイルのオブジェクトを取得し、その'rpc_server_socket_path'にアクセスすることで "/tmp/server.sock" を取得することを試みました。

しかし、プロジェクトのルートディレクトリである、05_remote_procedure_call/ をカレントディレクトリとして、コマンドpython3 src/server/server.py は実行できましたが、server/ をカレントディレクトリとしたコマンド python3 server.py は実行できませんでした。

そこで、Pythonのスクリプトがどこから実行されても、設定ファイル(config.json)を確実に見つけることができれば、解決できると考えました。

4. 解決策: pathlib の使用

4-1. pathlib とは

標準ライブラリにあるモジュールで、ファイルやディレクトリのパスをオブジェクト指向で扱うためのライブラリです。

pathlib は、実行しているOS(Windows、Linux、macOSなど)のパスの区切り文字(Unix系なら /、Windowsなら \)を自動的に判断し、適切な形式で処理してくれるため、OSの違いを意識する必要がなくなります。

また、ディレクトリとファイル名をスラッシュ(/)演算子で結合できることが特徴です。

from pathlib import Path

config_path = Path(root_dir) / 'config.json'

pathlib と parent を使用することで階層を遡るのが簡単になります。

from pathlib import Path

# Path(__file__) は現在のファイルのPathオブジェクト
# .parent で1つ上のディレクトリに移動 (src/server -> src/)
# 以下では3つ上のディレクトリに移動
root_dir = Path(__file__).parent.parent.parent 
config_path = root_dir / 'config.json'

4-2. __file__ とは

Pythonにおいて現在実行されているファイルのパス(ファイル名を含む完全なパス、または相対パス)を保持する特別な変数のことです。

# Path(__file__)の確認
print(Path(__file__))
# /Users/myname/project/backend-portfolio/05_remote_procedure_call/src/server/server.py

この性質を踏まえて、先ほどの例の内容を確認します。

# 3つ上のディレクトリを格納
root_dir = Path(__file__).parent.parent.parent
print(root_dir)
# /Users/myname/project/backend-portfolio/05_remote_procedure_call/

# 05_remote_procedure_call/config.json というパスを作成
config_path = root_dir / 'config.json'
print(config_path)
# /Users/myname/project/backend-portfolio/05_remote_procedure_call/config.json

# これにより、指定されたパスにあるJSON形式の設定ファイルを読み込み、その内容をPythonのオブジェクト(通常は辞書)としてconfig変数に格納します
config = json.load(open(config_path))

print(config)
# {'rpc_server_socket_path': '/tmp/server.sock'}
# キー: 'rpc_server_socket_path', 値: '/tmp/server.sock'

print() は状況を可視化するために使用しており、実際のプログラムには使用していません。

最下層に存在する server.py のフルパスを表示し、そこから3つ上の階層のディレクトリである 05_remote_procedure_call をルートディレクトリとして Path オブジェクトが root_dir に格納されます。

これにより、05_remote_procedure_call 配下にある config.json へのファイルパスが取得でき、config.json 内で設定したファイルパスをサーバーのアドレスとして設定することができ、ファイルが見つからない FileNotFoundError を回避することができました。

5. まとめ

open() で指定するファイルパスが、カレントディレクトリに依存しない絶対パスを作成することでファイルが見つからない FileNotFoundError を回避する方法として、pathlib という標準ライブラリのモジュールを紹介させていただきました。

また、Pythonには __file__ のような特殊な変数があることも紹介させていただきました。
ファイルパスに起因するエラーの解決法として、pathlib と __file__ の組み合わせがあります。
ファイルパスの問題の解決策の1つとしてお役に立てれば幸いです。

最後までお読みいただき、ありがとうございました。

参考URL

https://note.nkmk.me/python-pathlib-absolute-relative/

https://docs.python.org/ja/3/library/pathlib.html

https://docs.python.org/ja/3/reference/datamodel.html#module.__file__

Discussion