やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Pythonにおけるファイルパス操作が難しい

使えるモジュール、関数、その組み合わせが非常に煩雑。

ファイルパスは厄介

  • 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.pathpathlibでは簡単にできない。

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)