Python pathlibを利用した、どこから実行しても読み込めるパスの書き方

三行で

  • __file__は実行したファイルのパスを取得する
  • pathlib.Path(__file__)で、実行したファイルのパスの階層を変更できる
  • pathlib.Path(__file__)を使ってパスを指定すると、どこから実行しても読み込めるパスが書ける

初めに

仕事で__file__の使い方を学んだので、備忘録として残しておきます。

dir構成

親ファルダ名はwrite_blog_file、読み込みたいデータdata/tmp.txtの中に置いてあり、実行するファイルはsrc/main.pyを想定しています。

.
└── write_blog_file
    ├── data
    │   └── tmp.txt
    └── src
        └── main.py

実行する場所が制限されるパスの書き方

ファイルの中身

tmp.txt

hello world!

main.py

with open('../data/tmp.txt', 'rt') as f:
    tmp = f.read()
print(tmp)

この../data/tmp.txtが問題となります。

自分がwrite_blog_file/srcにいるとき

lsをすると

main.py

が返ってくる状況で、

python main.py

を実行すると、

hello world!

という出力結果が得られます。tmp.txtの中身を読み込めていることがわかります。

自分がwrite_blog_fileにいるとき

lsをすると

data src

が返ってくる状況で

python src/main.py

を実行すると、

FileNotFoundError: [Errno 2] No such file or directory: '../data/tmp.txt'

と言うエラーが出力されます。 write_blog_file/srcにいたときは../data/tmp.txtのパスであってましたが、write_blog_fileにいると、相対パスが合わなくなったためですね。

ここで、どこから実行しても、常にdata/tmp.txtを読み込めるようにしたい気持ちが生まれます。

どこから実行しても読み込めるパスの書き方

pathlib.Path(__file__)は実行ファイルのパスを取得する

この気持ちはpathlib.Path(__file__)にて実現できます。

pathlib.Path(__file__)がどんなものを取得するのか説明するために、新しいファイルcheck_file_output.pyをsrc配下に一つ追加します。

check_file_output.py

from pathlib import Path

print(Path(__file__))
print(Path(__file__).parent)

これをwrite_blog_file/srcにて下記のように実行します。

python check_file_output.py

実行結果は以下です。

<my_path>/write_blog_file/src/check_file_output.py
<my_path>/write_blog_file/src

<my_path>の部分は、人によって異なるパスが入ります。

これを見ると

  • Path(__file__)によって実行したファイルの絶対パスが取得できていること
  • Path(__file__).parentによって、一つ上の階層を取得できていること

が分かります。

Path(__file__)絶対パスを返すのはpythonのversionが3.9からの仕様です。3.8以下の場合はPath(__file__)Path(__file__).resolve()と置き換えて下さい。

つまり、Path(__file__)によって、実行ファイルのパスをベースにして、階層を変更したパスを取得可能であることが分かります。

これを利用すると、どこからファイルを実行しても読み込めるパスの書き方が実現できます。

実行しているファイルの場所と、読み込むファイルの位置関係は常に固定のため、実行しているファイルの場所からの相対パスで読み込むファイルを指定するイメージです。

実際に見てみましょう。

pathlibを利用して、どこからでも実行できるパスを書く

先ほどのmain.pyを、pathlibを利用して書くとこうなります。

main_use_pathlib.py

from pathlib import Path

with open(f'{Path(__file__).parents[1]}/data/tmp.txt') as f:
    tmp = f.read()
print(tmp)

parents[1]は2階層上がることを意味します。

このコードで、先ほどと同様実行する場所を変えてみましょう。

自分がwrite_blog_file/srcにいるとき

下記コマンドを実行します。

python main.py

結果は、

hello world!

となります。

自分がwrite_blog_fileにいるとき

下記コマンドを実行します。

python main.py

結果は、

hello world!

となります。

よって、実行する場所を変えても、ファイルを読み込めるようなパスが書けました。

参考

docs.python.org

note.nkmk.me