あなたは、もうos.pathやglobやopenを使わなくていい

はじめに

Python3.3までは、 「あるディレクトリ以下に存在するテキストファイルの一覧を取得して、それぞれを開く」 という処理に、以下の3つのライブラリを使う必要がありました。

  • ファイルパス操作
    os.path
  • ファイルの一覧取得
    glob.glob
  • ファイルを開く
    open

しかし、Python3.4以降であれば、これらを すべて 標準ライブラリpathlibに一任 することができます。
これによって、ファイル操作処理における 書きやすさ・読みやすさが向上 が向上し、それぞれのライブラリの使い方をわざわざ調べる時間や、 ライブラリ間の微妙な仕様の違いに苛まれる心配から解放されます。

今回は、 ../datasets/foo/bar以下に存在するテキストファイルをすべて取得して、内容を表示したい」 という目標に対して、pathlibを使わない場合、pathlibを使った場合のコードを比較しましょう。

pathlibを利用しない場合

まずは、os.path、glob.glob、openを使った場合のコードを見てみましょう。

import glob
from os.path import join, dirname, abspath

current_dir = dirname(abspath(__file__))
datasets_dir = join(dirname(current_dir), 'datasets', 'foo', 'bar')
file_names = glob.glob(datasets_dir + '/**/*.txt', recursive=True)

for file_name in file_names:
    with open(file_name, 'r') as f:
        print(f.read())

問題1: パッと見て、どこを指しているのか分からない

あなたは、以下のコードを見て、これがどこを指しているのかすぐに分かりますか?

current_dir = dirname(abspath(__file__))
datasets_dir = join(dirname(current_dir), 'datasets', 'foo', 'bar')

普段、私達はディレクトリパスを左から右に読みますし、英語や日本語も左から右に読みます。しかし、joinやdirnameやabspathを使うと、そのコードがどこのパスを示しているのか、左から右にスラスラ読むことはできません。

問題2: typoを誘発する

datasets_dir = join(dirname(current_dir), 'datasets', 'foo', 'bar')
file_names = glob.glob(dir_name + '/**/*.txt', recursive=True)

os.path.joinでは、引数のパスの先頭に/がありません。さもなくば、絶対パスとして扱われ、全く別の場所を参照してしまいます。
一方、glob.globでは、引数のパスの先頭に/を付けています。さもなくば、意図したパスとは異なる場所を参照してしまいます。

よくよく考えてみれば当たり前のことですが、タイプミスやバグを誘発し、面倒です。

pathlibを利用した場合

from pathlib import Path

current_dir = Path(__file__).resolve()
datasets_dir = current_dir.parent.parent / 'datasets' / 'foo' / 'bar'
file_names = datasets_dir.glob('**/*.txt')

for file_name in file_names:
    print(file_name.read_text())

良いところ1: 読みやすい

pathlibを用いた場合、普段我々がディレクトリパスを読む時にそうしているように、左から右にディレクトリパスを追うことができます。

datasets_dir = current_dir.parent.parent / 'datasets' / 'foo' / 'bar'

は、「現在のディレクトリの、親の、親の、'datasets'の中の...」と、まさにそのまま左から右にスラスラと読むことができます。また、pathlibでは/演算子でパスを結合することができます。慣れないと少し気持ちが悪いかもしれませんが、読みやすいことは間違いありません。

良いところ2: globやopenの機能もpathlibひとつで解決

file_names = datasets_dir.glob('**/*.txt')

for file_name in file_names:
    print(file_name.read_text())

それだけではなく、ファイルの一覧の取得も datasets_dir.glob('**/*.txt')で一発です。
ファイルもいちいちopenせずにテキストを取得できます。バイナリを取得したい場合はread_bytes()を利用してください。

おわりに

他にも、詳しいことはすべて https://docs.python.jp/3/library/pathlib.html に記されています。本当に便利です。こちらもぜひ一度ご覧になってください。