備忘録とか日常とか

学んだこととかを書きます。

pathlibとかいう優秀すぎる標準ライブラリ(python)

自分は今までpythonでファイル操作を行うときはos.pathとかglobとかを使っていたが、pathlibという優秀なものがあると聞いて使ってみた。
python3.4以降なら使わない手はない。

以下優れている点

  • os, glob の組み合わせで行ってきた操作が大体pathlib一つでできるようになる
  • メソッドチェーン的に記述でき、可読性が上がる
  • open()などのファイル読み込み系の操作と親和性が高い
  • windowsだとファイルパスにエスケープ二つ\\入れる必要があるが、/区切りで解釈してくれる

以下もうひとつな点

  • 一部ライブラリではPathオブジェクトを受け付けず、str()でキャストする必要がある
  • copyなど一部未対応のメソッドがある

公式ドキュメントはこちら

主な使用方法

import, インスタンス生成

基本はPath オブジェクトを生成して操作することになる。
インスタンス生成時にファイルまたはディレクトリのパスを指定する。(絶対パスでも相対パスでもよい)

>>> from pathlib import Path
>>> p = Path('aaa/bbb/ccc.txt')

pはPathのサブクラスであり、windowsならWindowsPathlinux, maxならPosixPathオブジェクトとなる。
使い方はほぼ同じなので気にする必要はない。

引数を指定しないとカレントディレクトリのリンクが自動的にパスに入る。

>>> Path()
WindowsPath('.')

カレントディレクトリ、ホームディレクトリ取得

# カレントディレクトリ取得
>>> p.cwd()

# ホームディレクトリ取得
>>> p.home()

上記メソッドは新たなパスが格納されたPathオブジェクトを返す。
これに限らず、Pathオブジェクトのメソッドは新たなPathを返すため、文字列を直に操作する必要がなくなる。

また、上記メソッドの出力はインスタンス生成時の引数に依らず、常に同じである。
Pathオブジェクトならいつでもカレント、ホームディレクトリを呼び出せる。

絶対パス取得

まずは絶対パスを取得する。

# シンボリックリンクを解決して絶対パスを返す
>>> p = Path('.')
>>> p.resolve()
WindowsPath('D:/project/path_test')

# なんかこっちでもいけるっぽい
>>> p.absolute()

公式ドキュメントには書いてなかったが、p.absolute()でも同じことができるっぽい。内部では同じメソッドを呼び出しているのだろうか。
コードの中身を見てないのでわかりません。

ついでに絶対パスかどうかの確認

>>> p.is_absolute()
True

絶対パスならTrueを返す。

ファイル名、拡張子の取得

ファイルまでのパスがPathオブジェクトに入っているなら以下でファイル名を取得できる
拡張子や、拡張子を除いた名前部分(stem)を取得することもできる。戻り値は文字列なので注意

# ファイル名取得 戻り値は文字列
>>> p.name
'aaa.txt'

# 拡張子取得  戻り値は文字列
>>> p.suffix
'.txt'

# .tar.gzみたいなのは.suffixes でリスト取得できる
>>> p.suffixes
['.tar', '.gz']

# 拡張子を除いたファイル名を取得 戻り値は文字列
>>> p.stem
'aaa'

ファイル、ディレクトリ削除、権限変更

パスがファイルかディレクトリかでメソッドが変わる。

# ファイル削除
>>> p.unlink()

# ディレクトリ削除 ただし空ディレクトリに限る
>>> p.rmdir()

# 権限変更 ファイル、ディレクトリ問わず適用可能
>>> p.chmod(755)
>>> p.cwd(755)

ディレクトリが空じゃない場合はおとなしくshutil.rmtreeとかを使う。

ディレクトリ取得

便利。

# 親ディレクトリ取得 戻り値はPATHオブジェクト
>>> p.parent

パス結合

これが一番便利かもしれない。
Pathオブジェクトでは/演算子がパスの結合として再定義されている。

# Pathオブジェクトに文字列で除算するとパス結合されたPathオブジェクトが返る
>>> Path('a/b/c') / 'd'
Path('a/b/c/d')

#  windowsでのエスケープ二つと混ぜても同じ結果が得られる
>>> Path('a\\b\\c') / 'd'
WindowsPath('a/b/c/d')

# 一応メソッドもある
>>> Path('a/b/c').joinpath('d')
WindowsPath('a/b/c/d')

ファイル一覧取得、検索

指定したパス内のファイル、ディレクトリを一覧表示・検索できる。
検索に関しては、標準ライブラリのglobと使い方がほぼ同じである。
ただし、いずれもメソッドの戻り値はジェネレータであることに注意する。

# パス内のファイル、ディレクトリ一覧のジェネレータを返す
>>> p.iterdir()
<generator object Path.iterdir at 0x00000197DA6D3BF8>

# パス内の.py ファイルを検索、ジェネレータを返す
>>> p.glob('*.py')
<generator object Path.glob at 0x000002804AEC6C50>

# リストとして取り出したければキャストする
>>> list(p.glob('*.py'))

# パス以下を再帰的に検索したい場合は以下のように指定する
>>> p.glob('**/*.py')

ファイルを開く

Pathオブジェクトにはファイルを開くメソッドも用意されている。
テキストとして開くならこれで問題ない。当然だがパスはファイルが指定されている必要がある。

# ファイルを開く
>>> f = p.open('r')

# 組み込み関数にPathオブジェクトを渡すこともできる
>>> f = open(p, 'r')

注意すべきは、Pathオブジェクトに対応していない外部ライブラリも存在するということ。
ここによると、どうやらopenCVcv2.imreadには対応していないらしい。

そんな時は文字列にキャストしてからライブラリに渡せば問題ない。

>>> p = Path('aaa/bbb/ccc.txt')
# str()でキャストできる  UNIX環境
>>> str(p)
'aaa/bbb/ccc.txt'

# windows 環境でも使える
>>> str(p)
'aaa\\bbb\\ccc.txt'

# .as_posix()でもできるが、windows環境下でも / で区切られる
>>> p.as_posix()
'aaa/bbb/ccc.txt'

as_posix()は / で区切られた文字列が返されるため、windows環境では使用できない。
じゃあwindows用のメソッドがあるかと思いきや用意されていないっぽいので、環境に依存したくなければstr()を使うようにすべきか。

判定系

以下のメソッドはTrueかFalseで判定を返す。

# 絶対パスかどうかを判定
>>> p.is_absolute()

# 現在のパスが与えられたパターンと一致するかどうかを判定 (パターンはglob形式で指定)
>>> p.match('a/*txt')

# パスのファイル、またはディレクトリが存在しているかどうかを判定
>>> p.exists()

# ディレクトリかどうか判定
>>> p.is_dir()

# ファイルかどうか
>>> p.is_file()

# シンボリックリンクかどうか
>>> p.is_symlink()

# Pathオブジェクトが指定するファイルと引数で指定したファイルが同じファイルを参照しているかどうか
# 引数にはPathオブジェクトか文字列をとる
>>> p.samefile(other_path)

ファイル名変更など

# 指定したファイルまたはディレクトリをtgtにリネームする
>>> p.rename(tgt)

# パスの末尾のファイルまたはディレクトリ名をtgtに置き換える
# 存在する場合はリネームする
>>> p.replace(tgt)