Python, pathlibでファイル一覧を取得(glob, iterdir)

Modified: | Tags: Python, ファイル処理

Pythonのpathlibモジュールのglob()iterdir()を使ってディレクトリ(フォルダ)内のファイルやサブディレクトリの一覧を取得する方法を説明する。再帰的に処理したり、ワイルドカード文字*や正規表現による条件を指定して一覧を抽出したりできる。

Pathオブジェクトの一覧が取得できるので、それぞれに対して各種メソッドを使った操作を行うことも簡単。

pathlibの基礎的な使い方については以下の記事を参照。

本記事のサンプルコードでは以下のようにpathlibモジュールからPathクラスをインポートしている。また、結果を見やすくするためにpprintもインポートしている。pathlibおよびpprintは標準ライブラリに含まれているので追加のインストールは不要。

from pathlib import Path
import pprint

以下のようなファイル・ディレクトリ構成を例とする。

temp/
├── 1.txt
├── 12.text
├── 123.txt
├── [x].txt
├── aaa.text
└── dir/
    ├── 987.text
    ├── bbb.txt
    ├── sub_dir1/
    │   ├── 98.txt
    │   └── ccc.text
    └── sub_dir2/
        └── ddd.text

ディレクトリ内のファイル・サブディレクトリ一覧を取得: iterdir()

ディレクトリ内のファイル・サブディレクトリ一覧を取得するにはPathオブジェクトのiterdir()メソッドを使う。

Path()Pathオブジェクトを生成する。

p_temp = Path('temp')

print(type(p_temp))
# <class 'pathlib.PosixPath'>

OSによってPosixPathまたはWindowsPathのインスタンスが生成される。Pathの基本については以下の記事を参照。

iterdir()メソッドはos.listdir()に似ているが、名前の通りリストではなくイテレータを返す。for文で使うのであればイテレータのままでよいが、リストにしたい場合はlist()を使う。

各要素はPathPosixPathまたはWindowsPath)オブジェクト。

print(type(p_temp.iterdir()))
# <class 'generator'>

pprint.pprint(list(p_temp.iterdir()))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

ファイルのみまたはディレクトリのみを抽出する例は後述。

ファイルを指すPathオブジェクトからiterdir()を呼ぶとエラー。

# print(list(Path('temp/1.txt').iterdir()))
# NotADirectoryError: [Errno 20] Not a directory: 'temp/1.txt'

iterdir()ではサブディレクトリ内のファイルやディレクトリを再帰的に取得することはできない。再帰的に一覧を取得したい場合は次に説明するglob()を使う。

条件を指定して再帰的に一覧取得: glob()

条件を指定して再帰的にファイル・サブディレクトリ一覧を取得するにはPathオブジェクトのglob()メソッドを使う。

名前の通り、処理としてはglobモジュールと同じ。globモジュールの詳細は以下の記事を参照。

globモジュールのglob()はリストを返すが、Pathオブジェクトのglob()はイテレータを返す。for文で使うのであればイテレータのままでよいが、リストにしたい場合はlist()を使う。

以下はサブディレクトリ内を含むすべてのディレクトリの中から拡張子がtxtのファイルの一覧を取得する例。

各要素はPathオブジェクト。

p_temp = Path('temp')

print(type(p_temp.glob('**/*.txt')))
# <class 'generator'>

pprint.pprint(list(p_temp.glob('**/*.txt')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

glob()で使える特殊文字は以下の通り。**については次に説明する。

  • *: 長さ0文字以上の任意の文字列
  • ?: 任意の一文字
  • []: 括弧内の文字列のどれか一文字

各特殊文字を使った例を示す。使い方はglobモジュールと同じなので、詳しい説明は上述の関連記事を参照されたい。

pprint.pprint(list(p_temp.glob('*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint(list(p_temp.glob('dir/*/*.text')))
# [PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

pprint.pprint(list(p_temp.glob('???.*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint(list(p_temp.glob('[a-z][a-z][a-z].*')))
# [PosixPath('temp/aaa.text')]

globモジュールとpathlibモジュールでの**の意味の違い

globモジュールとpathlibモジュールではglob()における**の意味が若干異なる。

  • globモジュールのglob()での**
    • 引数recursive=Trueの場合にのみ再帰的な処理を行う
    • ファイルにもマッチする
  • pathlibモジュールのglob()での**
    • 引数recursiveは存在せず、**を使うと常に再帰的な処理を行う
    • ファイルにはマッチしない

具体例を示す。

globモジュールのglob()recursive=Trueとして**を使うと、すべてのサブディレクトリを再帰的に走査し、ファイル・ディレクトリの一覧を返す。デフォルト(recursive=False)では再帰処理は行われない。

import glob

pprint.pprint(glob.glob('temp/**', recursive=True))
# ['temp/',
#  'temp/[x].txt',
#  'temp/aaa.text',
#  'temp/dir',
#  'temp/dir/sub_dir1',
#  'temp/dir/sub_dir1/98.txt',
#  'temp/dir/sub_dir1/ccc.text',
#  'temp/dir/987.text',
#  'temp/dir/bbb.txt',
#  'temp/dir/sub_dir2',
#  'temp/dir/sub_dir2/ddd.text',
#  'temp/1.txt',
#  'temp/12.text',
#  'temp/123.txt']

pprint.pprint(glob.glob('temp/**'))
# ['temp/[x].txt',
#  'temp/aaa.text',
#  'temp/dir',
#  'temp/1.txt',
#  'temp/12.text',
#  'temp/123.txt']

pathlibモジュールのglob()では**はディレクトリにのみマッチする。ファイルにもマッチさせるには**/*とする。**/*では現在のディレクトリは含まれないので注意。

p_temp = Path('temp')

pprint.pprint(list(p_temp.glob('**')))
# [PosixPath('temp'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/dir/sub_dir1'),
#  PosixPath('temp/dir/sub_dir2')]

pprint.pprint(list(p_temp.glob('**/*')))
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1'),
#  PosixPath('temp/dir/987.text'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir2'),
#  PosixPath('temp/dir/sub_dir1/98.txt'),
#  PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

正規表現で条件指定

glob()で使えるのは限られた特殊文字のみだが、正規表現による条件指定と組み合わせることも可能。

リスト内包表記の条件分岐でre.search()による判定を行う。Pathオブジェクトをstr()で文字列に変換する必要があるので注意。

例は以下の通り。

import re

p_temp = Path('temp')

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search(r'\d+\.txt', str(p))])
# [PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search(r'/\D{3}\.(txt|text)', str(p))])
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/dir/bbb.txt'),
#  PosixPath('temp/dir/sub_dir1/ccc.text'),
#  PosixPath('temp/dir/sub_dir2/ddd.text')]

\dは数字、+は1回以上の繰り返し。\Dは数字以外の文字、{n}はn回の繰り返し。また、(a|b)はa, bのいずれかとなる。globだけでは実現できない複雑な条件が指定できる

Pathオブジェクト一覧に対する処理の例

iterdir(), glob()いずれもPathオブジェクトの一覧が取得できるので、それらのオブジェクトに任意の処理を実行できる。

いくつか具体例を示す。

すべて絶対パスに変換

絶対パスに変換するのはresolve()

pprint.pprint([p.resolve() for p in p_temp.iterdir()])
# [PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/[x].txt'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/aaa.text'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/dir'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/1.txt'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/12.text'),
#  PosixPath('/Users/mbp/Documents/my-project/python-snippets/notebook/temp/123.txt')]

すべて文字列に変換

Pathオブジェクトはstr()関数で文字列に変換できる。

pprint.pprint([str(p) for p in p_temp.iterdir()])
# ['temp/[x].txt',
#  'temp/aaa.text',
#  'temp/dir',
#  'temp/1.txt',
#  'temp/12.text',
#  'temp/123.txt']

ファイルまたはディレクトリのみ抽出

is_file(), is_dir()でファイルまたはディレクトリかを判定できる。リスト内包表記の条件分岐でそれらのメソッドを使うとファイルまたはディレクトリのパスのみを抽出可能。

pprint.pprint([p for p in p_temp.iterdir() if p.is_file()])
# [PosixPath('temp/[x].txt'),
#  PosixPath('temp/aaa.text'),
#  PosixPath('temp/1.txt'),
#  PosixPath('temp/12.text'),
#  PosixPath('temp/123.txt')]

pprint.pprint([p for p in p_temp.iterdir() if p.is_dir()])
# [PosixPath('temp/dir')]

ファイル名(basename)のみを抽出

ファイル名(basename)はname属性で取得できる。ディレクトリのname属性はディレクトリ名そのものになる。

この例ではis_file()と組み合わせてディレクトリは除外してファイル名のみ抽出している。

pprint.pprint([p.name for p in p_temp.iterdir() if p.is_file()])
# ['[x].txt', 'aaa.text', '1.txt', '12.text', '123.txt']

条件で抽出したファイルを削除

glob()の条件で抽出したファイルをunlink()で削除する。

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search(r'\d+\.txt', str(p))])
# [PosixPath('temp/1.txt'),
#  PosixPath('temp/123.txt'),
#  PosixPath('temp/dir/sub_dir1/98.txt')]

for p in p_temp.glob('**/*'):
    if re.search(r'\d+\.txt', str(p)) and p.is_file():
        p.unlink()

pprint.pprint([p for p in p_temp.glob('**/*')
               if re.search(r'\d+\.txt', str(p))])
# []

以下のようにリスト内包表記で一行で書くことも可能。

[p.unlink() for p in p_temp.glob('**/*') if re.search(r'\d+\.txt', str(p)) and p.is_file()]

ディレクトリを削除したい場合はis_dir()で判定した上でshutil.rmtree()を使う。Pathオブジェクトのメソッドrmdir()は空のディレクトリのみが対象なので注意。

関連カテゴリー

関連記事