【Python】フォルダー内の対象ファイルだけ集める:globと除外条件

PythonのTopに戻る

見つかったファイルをそのまま全件処理しない

サブフォルダーに分かれたCSVを集めるならPath.rglob()が使える。ただし拡張子だけで選ぶと、前回の出力や一時ファイルも対象に混ざり、処理を繰り返すたびに結果が増えることがある。探索・除外・順序付けを一つの入口にまとめ、実際の読み込みを始める前に対象一覧を確定させると確認しやすい。

ここでは.csvという小文字拡張子だけを集め、outとcache以下、ドットで始まる名前、一時ファイル、シンボリックリンクを除外する。大文字小文字の扱いをOS任せにせず、Python 3.12のcase_sensitive=Trueで明示する。処理順は相対パスの文字列順とし、自然順への並べ替えは別の記事で扱う。

相対パスの各部分で除外する

example.py

from pathlib import Path
from tempfile import TemporaryDirectory


def collect_csv(root):
    root = Path(root)
    targets = []
    for path in root.rglob("*.csv", case_sensitive=True):
        relative = path.relative_to(root)
        if any(part in {"out", "cache"} or part.startswith(".") for part in relative.parts):
            continue
        if path.name.startswith("~") or path.is_symlink() or not path.is_file():
            continue
        targets.append(relative)
    return sorted(targets, key=lambda path: path.as_posix())


with TemporaryDirectory() as temporary:
    root = Path(temporary)
    for name in ("b.csv", "raw/a.csv", "raw/UPPER.CSV", "out/old.csv", "cache/x.csv", ".hidden.csv", "raw/~work.csv"):
        path = root / name
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text("value\n1\n", encoding="utf-8")
    (root / "linked.csv").symlink_to(root / "b.csv")
    (root / "linked_dir").symlink_to(root / "raw", target_is_directory=True)
    result = [path.as_posix() for path in collect_csv(root)]
    assert result == ["b.csv", "raw/a.csv"]
    print(result)

実行結果

['b.csv', 'raw/a.csv']

拡張子だけでなく場所も条件にする

relative_to(root)で基準フォルダーからの相対パスを得る。partsはパスを構成する名前の組なので、outがパスのどこか一段に現れたら除外できる。文字列の部分一致でoutを探すと、output_notes.csvのような意図しない名前まで除外してしまう。フォルダー名を判定したいのか、ファイル名を判定したいのかを分ける。

rglob()の結果は処理に都合のよい順序とは限らないので、sorted()で順序を確定する。複数ファイルを連結したり、最初のファイルを代表にしたりする処理では、この違いが結果に影響する。as_posix()は区切りを/にした比較用の表記であり、ファイルを別の場所へ移す操作ではない。

is_file()で通常のファイルに絞るが、それだけではシンボリックリンク経由のファイルも真になる場合がある。そのためis_symlink()を先に確認している。名前が.csvで終わるフォルダーなども読み込み対象から外れる。拡張子が合うことと、CSVとして正しく読めることは別なので、読み込み時の列数や型の確認は後続処理で行う。

探索範囲とリンクの条件

確認したPython 3.12では、このrglobの再帰探索はディレクトリのシンボリックリンクをたどらず、例のlinked_dir以下は一覧に現れない。新しいPythonには探索に関する追加引数があるが、この例の実行版と混同しない。シンボリックリンクを作成する部分はLinuxで実行したもので、Windowsでは権限や開発者モードなどの条件が異なる。

ここでの除外は、見つかった候補を一覧へ加えない処理である。out以下の探索そのものを必ず省略する仕組みではない。非常に大きな除外フォルダーがある場合は、os.walk()などで探索するディレクトリを途中で絞る方式を検討する。結果が少ないから探索コストも小さい、とは限らない。

探索中に他のプロセスがファイルを追加・削除すれば、一覧と読み込み時の状態が違う可能性がある。対象一覧を作ったことはディレクトリの固定されたスナップショットを得たことではない。読み込み時のFileNotFoundErrorやPermissionErrorも扱い、入力件数が想定と違う場合はそのまま処理を続けるかを決める。

実データへ向ける前の確認

例は一時ディレクトリに作った7種類の名前と2種類のリンクだけを扱う。実フォルダーへ適用するときは、まず相対パスの一覧と件数を表示し、空の一覧や前回出力の混入を確認する。リンクやフォルダーを敵対的に差し替えられる環境への防御には、この事前判定だけでは十分でない。信頼できる作業領域での対象選定として利用する。

実行環境と関連情報

掲載コードはLinux上のCPython 3.12.14で実行した。OS固有のファイル操作や対話環境の違いは、本文に記した条件に従って扱う。

関連:file2よりfile10が先に来る?番号付きファイルを自然順に並べる / フォルダーのファイル台帳を作る:パス・サイズ・更新日時の収集

PythonのTopに戻る