静かなる名辞

pythonとプログラミングのこと


【python】実行スクリプトからの相対パスでファイルを開く

概要

 pythonのファイルオープンの際のカレントディレクトリは実行時のshellのカレントディレクトリを引き継ぐ仕様なので、スクリプト基準の相対パスだと考えていると悲しい思いをします。そこで、この記事ではその対応策について説明します。

悲しい思い

 具体的にはこんな感じです。

 まず、以下のようなディレクトリがあるとします。

$ tree hoge
hoge
├── aaa.txt
└── test1.py

0 directories, 2 files

 中身はこうなっています。

$ cd hoge
$ cat aaa.txt 
aaa
$ cat test1.py 
with open("aaa.txt") as f:
    print(f.read())

 この時点でのカレントディレクトリ(shell)はhogeの中です。test1.pyを実行してみます。

$ python test1.py 
aaa

 うまくいっているようなので、一つ上のディレクトリに移ってまた実行します。

$ cd ..
$ python hoge/test1.py 
Traceback (most recent call last):
  File "hoge/test1.py", line 1, in <module>
    with open("aaa.txt") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'aaa.txt'

 今度は駄目でした。

 実行スクリプトからの相対パスでできてくれると嬉しいのですが、そういう風には動かないのです。

対応策

 対策として、スクリプトの絶対パスを拾って動かす方法……がqiitaの記事にありました。

実行スクリプト名は__file__で取得できる.
これとos.pathモジュールを組み合わせて,実行スクリプトから相対パスでファイルにアクセスする.

base = os.path.dirname(os.path.abspath(__file__))
name = os.path.normpath(os.path.join(base, '../local/local_db'))

実行スクリプトからの相対パスでファイルにアクセスする. - Qiita

 面倒くさ・・・。

 ん、それならいっそカレントディレクトリを__file__に張り替えれば良いのでは。それで後から元に戻せば副作用はない(マルチスレッドでもない限りは)。

$ cat hoge/test2.py 
import os

tmp = os.getcwd()
os.chdir(os.path.dirname(os.path.abspath(__file__)))
with open("aaa.txt") as f:
    print(f.read())
os.chdir(tmp)
$ python hoge/test2.py 
aaa

$ cd hoge
$ python test2.py 
aaa

 実行スクリプトのディレクトリの絶対パスを拾うために結局os.path.abspathとか使ってるし、あまりスマートにはならなかったですね。

 ちなみに、python3.4以降はこんなのもあるらしいですが、もはや魔法の呪文じみてる気が・・・(pathlibを使いこなせれば大丈夫なのでしょうけど)。

from pathlib import Path
print(Path(__file__).resolve().parent)

スクリプトの存在するディレクトリーの絶対パスを取得する - Qiita

まとめ

 けっこう厄介なので絶対パスで書いてしまうことも多いですが、実行スクリプトからの相対パスでもファイルの在処を取得してやればできる、ということで。この方法だと、ファイルの相対的な位置関係さえ合っていればどこに置いても動きます。