【Python】標準ライブラリPathlibでおしゃれにPathを表記しよう!

こんにちは。DA事業本部の春田です。

今さらですが、最近Pythonの Pathlib という標準ライブラリを認知しました。

いやはや、めちゃめちゃ良いですねコレ。すでにご存知の方も多いかと思いますが、そのエレガントさに身を任せてご紹介していきます。

  • 実行環境:
    • macOS Mojave 10.14.6
    • Python 3.7.5

※PathlibはPython3.4以降で導入されています

基本

こんな構成のディレクトリ群を例とします。

animals  <- I'm here!
├── dogs
│   ├── shiba.py
│   ├── beagle.txt
│   └── pug
│       └── buchakawa.json
└── cats
│   ├── scottish.txt
│   └── doraemon.exe
└── hamaguchi.py

PathlibはPython3.4以降の標準ライブラリなので、シュパッと import できます。まずはメインクラスの Path を入れます。

>>> from pathlib import Path

Path の引数にパスを含んだ文字列を渡すと、 PosixPath インスタンスが返されます。(実行環境がWindowsの場合は WindowsPath ですかね?)このインスタンスで、あんなことやこんなことができるわけですね。存在確認は exists() メソッドを用います。

>>> dogs = Path('./dogs')
>>> dogs
PosixPath('dogs')
>>> print(dogs)
dogs
>>> dogs.exists()
True

Pathlibの最もエレガントな仕様は、パスを除算記号 / で繋げられる点だと思います。文字列を format したり + で繋げる方法よりも、記述量が減り読みやすくなります。要素の一部に PosixPath インスタンスが使われていれば、文字列でも繋げられます。

>>> shiba = dogs / 'shiba.py'
>>> shiba
PosixPath('dogs/shiba.py')
>>> shiba.exists()
True

shiba から buchakawa.json を指したいな、という時には、 parent もしくは parents プロパティの出番です。

>>> bucha1 = shiba.parent / 'pug' / 'buchakawa.json'
>>> bucha1
PosixPath('dogs/pug/buchakawa.json')
>>> bucha2 = shiba.parents[0] / 'pug' / 'buchakawa.json'  # スライスで親ディレクトリの位置を指定
>>> bucha2
PosixPath('dogs/pug/buchakawa.json')

ファイル名を取りたい場合は name 、拡張子が欲しい時は suffix 、拡張子を除いたファイル名が欲しい場合は stem といったプロパティも用意されています。本当にプロパティの種類が豊富ですね。

>>> bucha.name
'buchakawa.json'
>>> bucha.suffix
'.json'
>>> bucha.stem
'buchakawa'

応用

検索の glob メソッドではジェネレータ生成され、マッチしたPosixPathインスタンスが返されます。

>>> cwd = Path('.')
>>> cwd.glob('*')
<generator object Path.glob at 0x103e5cad0>
>>> [*cwd.glob('*')]
[PosixPath('dogs'), PosixPath('cats'), PosixPath('hamaguchi.py')]
>>> [*cwd.glob('cats/*.exe')]
[PosixPath('cats/doraemon.exe')]

Pathに任意の文字列が含まれているかどうか判定するには、 match メソッドを使います。右側から一致を調べていく仕様なので、それに合わせたパスも記述する必要があります。

>>> bucha
PosixPath('dogs/pug/buchakawa.json')
>>> bucha.match('pug')
False
>>> bucha.match('*/pug')
False
>>> bucha.match('pug/*')
True

僕のお気に入りは relative_to メソッドです。起点のパスからの相対パスを表現することができます。これを応用すると、任意のファイルに対してディレクトリの付替えなどができます。

>>> bucha
PosixPath('dogs/pug/buchakawa.json')
>>> pug = Path('dogs/pug')
>>> pug
PosixPath('dogs/pug')
>>> scottish = Path('cats/scottish.txt')
>>> scottish
PosixPath('cats/scottish.txt')
>>> scottish.parent / bucha.relative_to(pug)
PosixPath('cats/buchakawa.json')

ファイル名や拡張子を付け替えたい時は、 with_namewith_suffix メソッドが使えます。うわぁぁ!拡張子だけ変えたいのにっっ!!なんてことよくありませんか?

>>> bucha.with_name('mechakawa.yaml')
PosixPath('dogs/pug/mechakawa.yaml')
>>> scottish.with_suffix('.json')
PosixPath('cats/scottish.json')

>>> scottish.with_suffix('json') # '.'が必要です
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pathlib.py", line 843, in with_suffix
    raise ValueError("Invalid suffix %r" % (suffix))
ValueError: Invalid suffix 'json'

その他、スクリプト内でファイルシステムに触れるのはちょっと怖いな、という時は Path クラスの代わりに PurePath クラスを使用すると機能が制限されます。

>>> from pathlib import PurePath
>>> shiba = PurePath('dogs/shiba.py')
>>> shiba.name
'shiba.py'
>>> shiba.exists()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'PurePosixPath' object has no attribute 'exists'
>>> shiba.glob('../')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'PurePosixPath' object has no attribute 'glob'

凄いシンプルなライブラリですが、組み合わせ次第で可能性は無限大!!という感じですね。ただし、Pathクラスは文字列ではないので、PathLikeオブジェクトに対応していない関数やメソッドでは、一旦 str() などを噛ませる必要があります。その点だけちょっと面倒ですね。。

最後に

ご紹介しきれなかった機能もあるので、興味のある方は公式ドキュメントもご参照ください!

pathlib --- オブジェクト指向のファイルシステムパス — Python 3.8.1 ドキュメント