使えるモジュール、関数、その組み合わせが非常に煩雑。
ファイルパスは厄介
- OS (FileSystem) によってパスの仕様が違う
- パス区切り文字が違う
~など特殊な意味をもつ文字がある
たとえばWindowsならパス区切り文字がバックスラッシュだが、Linuxはスラッシュ。文字列操作で直接ハードコーディングすると、別のOSでは動作しない。
なので、異なるOSでも同じコードで同じ操作をするために、パス操作用のAPIを使う。Pythonにもそんな便利なAPIが用意されている。だが、そいつがまた厄介。
問題
Pythonにおけるファイルパス操作が難しい。
どう難しいのか、説明するのも難しい。ので、今まで書けなかった。いいかげんイラつくので書いてみる。
原因
いくつもある。
- os.path, pathlibの2つのモジュールがある
- バージョンごとに使えるモジュールが違う
- pathlibに統一したいができない
- ほかのメソッド等一部pathlibに未対応
- 名前に統一性がない
is_file(),isfile()- os.pathとpathlibには似た名前のメソッドがあるが微妙に違う
- pathlibの守備範囲が謎
モジュールを使い分けねばならない
複数のモジュールがある。バージョンごとに使えるものが違う。
| モジュール | 使えるver |
|---|---|
| os.path | 2〜 |
| pathlib | 3.4〜 |
pathlibが未実装の環境がある。os.pathを使えば間違いないが、コードが冗長になることがある。
pathlibに統一できない
pathlibに統一したいが、できない。文字列に変換せねば使えない場合がある。たとえばsys.path.append()するときは文字列に変換せねばならない。だが、変換しなくてもいいメソッドもある。その区別がつかない。
Python Doc にはしばしば以下のようなことが書いてある。
バージョン 3.6 で変更: path-like object を受け入れるようになりました。
これをみて「きっと今までpathを文字列で渡していた全メソッドでpathlibを引数に渡してうまいこと動くようになったのだろう」などと甘い期待をもつと裏切られる。
この統一感のないチグハグさが、使いづらさの理由のひとつ。
名前に統一性がない
is_file()とisfile()。どちらもファイルであるか、そのファイルが存在するかの確認をするメソッド。だが、os.path.isfile(), pathlib.Path.is_file()のように統一性がない。
名前についてのわかりにくさは、それぞれのドキュメントを眺めてもすぐに目に付く。
pathlibの守備範囲が謎
os.pathはパス文字列の操作に限定されている。ファイルやディレクトリ自体の操作は、os, shutilのような別のモジュールで行う。
だが、pathlibは中途半端にファイル操作ができる。ファイル削除はできないのに、ファイルを開ける。ディレクトリも空なら削除できる。じつに中途半端。結局、pathlibだけではすべてを賄えず、os, shutil, os.pathモジュールを併用することになることがよくある。
しっかり調べなければ、ほかのモジュールとの使い分けが必要なことも把握できない。pathlibは半端で完成度が低い印象。
じゃあos.pathだけ使えば良くね?
と思うじゃん? でも、以下のようなことができない。
n階層目の名前
/A/B/Cというパスがあったとき、先頭から二番目の名前Bがほしいとする。たとえば文字列操作だと'/A/B/C'.split('/')[2]のようにインデックスを指定すればできる。だが、os.pathやpathlibでは簡単にできない。
os.path.split(path)があるが、これはファイル名とディレクトリの2つに分離する関数である。使えない。
pathlibなら一応できる。pathlib.Path('/A/B/C').parents[0].nameという非常にわかりにくい字面になってしまうが。
じつはパス区切り文字はos.sepで取得できるので、'/A/B/C'.split(os.sep)[2]とすると一番イメージに近い。専用モジュールたちにはできない仕事である。
よく使うコード
コードファイル自身が存在するディレクトリパスを取得する
コード|型
------|--
pathlib.Path(__file__).parent|pathlib.Path
str(pathlib.Path(__file__).parent)|str
os.path.dirname(__file__)|str
os.sep.join(__file__.split(os.sep)[:-1])
相対パスでimportするモジュールを指定する
コードが汚くなるのが難点。
str(pathlib.Path('/tmp/work/A').parent.parent)