🐍

PythonのPathオブジェクトを使って検索すると便利

に公開

前提

  1. Google Colabは、ディレクトリ構成やGUIの機能が今後変更される可能性があることに注意してください。

  2. Colabを起動して、/content/sample_dataの下にtest dirとhoge.csvを作成した状況を前提とします。

/content# tree
.
└── sample_data
    ├── anscombe.json
    ├── california_housing_test.csv
    ├── california_housing_train.csv
    ├── mnist_test.csv
    ├── mnist_train_small.csv
    ├── README.md
    └── test
        └── hoge.csv

パスの検索の方法

Pythonのパス検索はPathオブジェクトで行うと非常に便利なのですが、意外と知られていないので紹介します。

見つけたコード

os.listdirでパス検索している例を見かけました。

import os

[f for f in os.listdir("sample_data") if f.endswith(".csv")]

出力 (List[str])

['mnist_train_small.csv',
 'california_housing_train.csv',
 'mnist_test.csv',
 'california_housing_test.csv']

おすすめ

個人的には次のようなコードがおすすめです。出力がより汎用性の高いPathオブジェクトになります。

コード

[f for f in Path("sample_data").glob("*.csv") if f.is_file()]

出力 (`List[Path]“)

[PosixPath('sample_data/mnist_train_small.csv'),
 PosixPath('sample_data/california_housing_train.csv'),
 PosixPath('sample_data/mnist_test.csv'),
 PosixPath('sample_data/california_housing_test.csv')]

Pathオブジェクトの便利なところ

Pathオブジェクトは便利なメソッドや属性が沢山生えています。strで扱うと自分でコードを書かないといけない部分をメソッド呼び出しや、属性参照で済むのです。

例えば、name属性を指定すればstr型でファイル名を取得できます。

PureWindowsPath('//some/share/setup.py').name
# 'setup.py'
PureWindowsPath('//some/share').name
# ''

また、拡張子の取得も簡単です。

PurePosixPath('my/library/setup.py').suffix
# '.py'
PurePosixPath('my/library.tar.gz').suffix
# '.gz'
PurePosixPath('my/library').suffix
# ''

さらに、/演算子によるディレクトリ探索もできます。便利。

p = PurePath('/etc')
# p
PurePosixPath('/etc')
p / 'init.d' / 'apache2'
# PurePosixPath('/etc/init.d/apache2')
q = PurePath('bin')
'/usr' / q
# PurePosixPath('/usr/bin')
p / '/an_absolute_path'
# PurePosixPath('/an_absolute_path')

親ディレクトリも簡単に呼び出せます。

p = PurePosixPath('/a/b/c/d')
p.parent
# PurePosixPath('/a/b/c')

引用: https://docs.python.org/ja/3.13/library/pathlib.html

応用例

ディレクトリの下で指定したフォーマットのファイルがあればリスト形式で出力するクラスを作ってみました。glob(), rglob()のおかげで、直下のみを出力するメソッドと再帰的にディレクトリを降下するコードを完結に記述できました。

コード

from pathlib import Path
from typing import List

class FileSearcher:
    def __init__(self, dir: str | Path):
        d = Path(dir)
        if not d.exists():
            raise ValueError("pathが存在しません")
        if not d.is_dir():
            raise ValueError("pathにディレクトリ以外が入力されました")
        self._dir = Path(d)

    def list_files_under_dir(self, extension: str) -> List[Path]:
        return [f for f in self._dir.glob(f"*.{extension}") if f.is_file()]

    def list_files_under_dir_recursively(self, extension: str) -> List[Path]:
        return [f for f in self._dir.rglob(f"*.{extension}") if f.is_file()]
    
def main() -> None:
    searcher = FileSearcher("./sample_data")
    print("list_files_under_dir (ディレクトリ直下ののみ)")
    for f in searcher.list_files_under_dir("csv"):
        print(f.name)
    print("="*20)
    print("list_files_under_dir_recursively (配下のディレクトリを探索)")
    for f in searcher.list_files_under_dir_recursively("csv"):
        print(f.name)

if __name__ == "__main__":
    main()

出力

recursivelyの方ではsample_data/test/hoge.csvが表示されている。

list_files_under_dir (ディレクトリ直下ののみ)
mnist_train_small.csv
california_housing_train.csv
mnist_test.csv
california_housing_test.csv
====================
list_files_under_dir_recursively (配下のディレクトリを探索)
mnist_train_small.csv
california_housing_train.csv
mnist_test.csv
california_housing_test.csv
hoge.csv

番外編

冒頭のこれをどのように作るのか気になった人がいるかもしれないので追記しておきます。

/content# tree
.
└── sample_data
    ├── anscombe.json
    ├── california_housing_test.csv
    ├── california_housing_train.csv
    ├── mnist_test.csv
    ├── mnist_train_small.csv
    ├── README.md
    └── test
        └── hoge.csv

Google ColabのVMはx8664のUbuntu 22.04です (2025年7月20日時点) Google Colabの左下にある、ターミナル機能で確認できます。

ターミナルを立ち上げて、OSを確認します。長いので折り畳みます。

osとアーキテクチャを確認する
/content# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
/content# uname -a
Linux a10a72f36c27 6.1.123+ #1 SMP PREEMPT_DYNAMIC Sun Mar 30 16:01:29 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

tree commandはデフォルトで当然入っていないので使いたい人は入れましょう。

tree commandをubuntuに入れる
/content# sudo apt install treeReading package lists... Done
Building dependency tree... Done
Reading state information... 0%Reading state information... Done
The following NEW packages will be installed:
  tree
0 upgraded, 1 newly installed, 0 to remove and 35 not upgraded.
Need to get 47.9 kB of archives.
not upgraded.
Need to get 47.9 kB of archives.
After this operation, 116 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 tree amd64 2.0.2-1 [47.9 kB]
Fetched 47.9 kB in 1s (77.8 kB/s)
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 78, <> line 1.)
debconf: falling back to frontend: Readline
Selecting previously unselected package tree.
(Reading database ... 126281 files and directories currently installed.)
Preparing to unpack .../tree_2.0.2-1_amd64.deb ...
Unpacking tree (2.0.2-1) ...
Setting up tree (2.0.2-1) ...
Processing triggers for man-db (2.10.2-1) ...

参考

Discussion