皆さんはPythonのpathlib使ってますか?私は非常によく使っています.例えば機械学習では学習の前に前処理を多く行うケースが非常に多いですが,このような時にpathlibを知っておくと便利です.pathlibは意外とPython 3.4(2014年~)からとそれなりに新しいため,古くからのPythonユーザーは os.pathの方をよく使っているかもしれません.ただpathlibは文字列ではなくPathクラスとして扱ってくれることで,例えばLinux/Windowsのパス表記の違いを吸収してくれたりします.
pathlibとos.pathの比較は公式のpathlibドキュメントに譲るとして,私がよく使うpathlibのクラスを紹介します.また今回改めてドキュメントを眺めて知った便利関数も多いので,公式ドキュメントに目を通すのもオススメします.
今回は下記のような複数の素性の異なるデータセットに対して処理するケースなどを考えてみます.
.
├── datasetA
│ ├── abc
│ │ ├── a.wav
│ │ └── b.wav
│ └── def
│ └── d.wav
└── datasetB
├── 123.wav
└── sub
└── 456.wav
便利関数26選
検索・走査系
所定ディレクトリ以下にある特定のファイルを再帰的に探したい
この時に使えるのが,globあるいはrglobです.個人的にはrglobの方がショートカット記法になっていて,正規表現のタイポが避けられるので好んで使っています.スペースの都合上 list化して表示しています.
from pathlib import Path
In [1]: list(Path(".").rglob("*.wav"))
Out[1]:
[PosixPath('datasetA/abc/a.wav'),
PosixPath('datasetA/abc/b.wav'),
PosixPath('datasetA/def/d.wav'),
PosixPath('datasetB/123.wav'),
PosixPath('datasetB/sub/456.wav')]
In [2]: list(Path(".").glob("**/*.wav"))
Out[2]:
[PosixPath('datasetA/abc/a.wav'),
PosixPath('datasetA/abc/b.wav'),
PosixPath('datasetA/def/d.wav'),
PosixPath('datasetB/123.wav'),
PosixPath('datasetB/sub/456.wav')]
サブディレクトリをリストアップしたい
os.listdir 相当の捜査です.
In [3]: list(Path(".").iterdir())
Out[3]: [PosixPath('datasetA'), PosixPath('datasetB')]
編集系
絶対パス内の相対パスを取得したい
例えばデータセット内のディレクトリ構造を維持したまま,新たに別のディレクトリにしてパスをすげ替えてファイルを書き出す場合などに有用です.
In [4]: Path("/home/jojonki/datasetA/abc.wav").relative_to("/home/jojonki")
Out[4]: PosixPath('datasetA/abc.wav')
ファイル名や拡張子を変更したい
これもよく使います.
In [5]: Path("datasetA/abc.wav").with_name("AAA.mp3")
Out[5]: PosixPath('datasetA/AAA.mp3')
In [6]: Path("datasetA/abc.wav").with_stem("AAA")
Out[6]: PosixPath('datasetA/AAA.wav')
In [7]: Path("datasetA/abc.wav").with_suffix(".mp3")
Out[7]: PosixPath('datasetA/abc.mp3')
In [8]: Path("datasetA/abc/b.wav").rename("datasetA/abc/c.wav")
Out[8]: PosixPath('datasetA/abc/c.wav')
宣言系
Pathインスタンスを生成したい
非常に直感的にPathクラスを作れます.例えば何らかのデータのディレクトリに対して動的にパスを作る際などに便利です.
In [9]: root_dir = Path("datasetA/abc")
In [9]: root_dir / "sub" / "def.wav"
Out[9]: PosixPath('datasetA/abc/sub/def.wav')
In [10]: Path("datasetA").joinpath("abc", "def.wav")
Out[10]: PosixPath('datasetA/abc/def.wav')
生成系
ディレクトリを生成したい
In [11]: Path("datasetC/").mkdir()
In [12]: Path("datasetD/1/2/3/4/5").mkdir(parents=True)
ファイルを読み書きしたい
これは知りませんでしたが便利そうです.
In [13]: Path("README.txt").write_text("# Title\nhello")
Out[13]: 13
In [14]: Path("README.txt").read_text()
Out[14]: '# Title\nhello'
アクセス系
これらもよく使うので頭の片隅に入れておくと良いと思います.
パスの存在を確認したり,絶対パスを取得したい
In [15]: Path("datasetA").exists()
Out[15]: True
In [16]: Path("datasetA").is_dir()
Out[16]: True
In [17]: Path("datasetA").is_absolute()
Out[17]: False
In [18]: Path("datasetA").absolute()
Out[18]: PosixPath('/Users/jonki/sandbox/datasetA')
ファイル名に関しての色々な情報を取りたい
In [19]: Path("datasetA/abc/a.wav")
Out[19]: PosixPath('datasetA/abc/a.wav')
In [20]: Path("datasetA/abc/a.wav").name
Out[20]: 'a.wav'
In [21]: Path("datasetA/abc/a.wav").stem
Out[21]: 'a'
In [22]: Path("datasetA/abc/a.wav").suffix
Out[22]: '.wav'
In [23]: Path("data.tar.gz").suffix
Out[23]: '.gz'
In [24]: Path("data.tar.gz").suffixes
Out[24]: ['.tar', '.gz']
In [25]: Path("datasetA/abc/a.wav").parent
Out[25]: PosixPath('datasetA/abc')
In [26]: Path("datasetA/abc/a.wav").parts
Out[26]: ('datasetA', 'abc', 'a.wav')
まとめ
以上Pythonのpathlibをまとめました.
昨今はChatGPTなどでやりたいことを伝えればChatGPTが生成してくれますが,今回紹介したようなコードは非常によく使うので覚えておいて損はないと思います.文字列同士を無理やり駆使してファイル名を変更していたりするコードがたまにありますが,pathlibを使えばそのようなこともなくなりますね.