使えるモジュール、関数、その組み合わせが非常に煩雑。
ファイルパスは厄介
- 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)