Pythonで、特定のディレクトリを除いたファイル一覧を取得することがあったため、メモを残します。
目次
環境
また、ディレクトリやファイル構成は以下の通りです。
$ pwd path/to/root/e.g._os_walk $ tree . ├── bar │ ├── foo.txt │ ├── fuga │ │ ├── ham.txt │ │ └── spam.txt │ └── hoge │ ├── ham.txt │ └── spam.txt ├── baz │ ├── fuga │ └── hoge ├── foo │ ├── foo.txt │ └── hoge │ ├── ham.txt │ └── spam.txt ├── make_file.py └── os_walk.py
上記の構成を再現する場合は、以下のPythonスクリプトを流します。
今回は、pathlibのPathでtouch()でファイルを作成しています。
utility - Implement touch using Python? - Stack Overflow
from pathlib import Path # parents=Trueとして、親ディレクトリがない場合は同時に作成 Path('foo/hoge').mkdir(parents=True, exist_ok=True) Path('foo/foo.txt').touch() Path('foo/hoge/spam.txt').touch() Path('foo/hoge/ham.txt').touch() Path('bar/hoge').mkdir(parents=True, exist_ok=True) Path('bar/fuga').mkdir(parents=True, exist_ok=True) Path('bar/foo.txt').touch() Path('bar/hoge/spam.txt').touch() Path('bar/hoge/ham.txt').touch() Path('bar/fuga/spam.txt').touch() Path('bar/fuga/ham.txt').touch() Path('baz/hoge').mkdir(parents=True, exist_ok=True) Path('baz/fuga').mkdir(parents=True, exist_ok=True)
os.walk()の動き
こんな感じのPythonスクリプトを用意し、動作を確認します。
root_dir = os.path.abspath(os.path.dirname(__file__)) for root, dirs, files in os.walk(root_dir): print('-'*10) print('root:{}'.format(root)) print('dirs:{}'.format(dirs)) print('files:{}'.format(files))
実行すると、bar -> baz -> fooの順でディレクトリを検索しているようでした。
$ python os_walk.py ---------- root:path/to/root/e.g._os_walk dirs:['bar', 'baz', 'foo'] files:['make_file.py', 'os_walk.py'] ---------- root:path/to/root/e.g._os_walk/bar dirs:['fuga', 'hoge'] files:['foo.txt'] ---------- root:path/to/root/e.g._os_walk/bar/fuga dirs:[] files:['ham.txt', 'spam.txt'] ---------- root:path/to/root/e.g._os_walk/bar/hoge dirs:[] files:['ham.txt', 'spam.txt'] ---------- root:path/to/root/e.g._os_walk/baz dirs:['fuga', 'hoge'] files:[] ---------- root:path/to/root/e.g._os_walk/baz/fuga dirs:[] files:[] ---------- root:path/to/root/e.g._os_walk/baz/hoge dirs:[] files:[] ---------- root:path/to/root/e.g._os_walk/foo dirs:['hoge'] files:['foo.txt'] ---------- root:path/to/root/e.g._os_walk/foo/hoge dirs:[] files:['ham.txt', 'spam.txt']
ファイルの一覧を取得
os.walk()を使って、ファイル一覧を取得してみます。
root_dir = os.path.abspath(os.path.dirname(__file__)) target_files = [] for root, dirs, files in os.walk(root_dir): targets = [os.path.join(root, f) for f in files] target_files.extend(targets) for f in target_files: print(f)
実行すると、空ディレクトリ(/baz/hoge
や/baz/fuga
)以外のファイル一覧を取得できました。
path/to/root/e.g._os_walk/make_file.py path/to/root/e.g._os_walk/os_walk.py path/to/root/e.g._os_walk/bar/foo.txt path/to/root/e.g._os_walk/bar/fuga/ham.txt path/to/root/e.g._os_walk/bar/fuga/spam.txt path/to/root/e.g._os_walk/bar/hoge/ham.txt path/to/root/e.g._os_walk/bar/hoge/spam.txt path/to/root/e.g._os_walk/foo/foo.txt path/to/root/e.g._os_walk/foo/hoge/ham.txt path/to/root/e.g._os_walk/foo/hoge/spam.txt
特定のディレクトリを除いたファイルの一覧を取得
公式ドキュメントを読むと、
topdown が True のとき、呼び出し側は dirnames リストを、インプレースで ( たとえば、 del やスライスを使った代入で ) 変更でき、 walk() は dirnames に残っているサブディレクトリ内のみを再帰します。これにより、検索を省略したり、特定の訪問順序を強制したり、呼び出し側が walk() を再開する前に、呼び出し側が作った、または名前を変更したディレクトリを、 walk() に知らせたりすることができます。
os.walk() | 16.1. os — 雑多なオペレーティングシステムインタフェース — Python 3.6.1 ドキュメント
とのことでした。
os.walk()の引数topdown
のデフォルト値はTrue
のため、dirnamesリストを変更すれば良さそうです。
今回は、hoge
ディレクトリ以下を除外したファイルの一覧を作成してみます。
スライス(dirs[:]
)で差し替え
まずは、dirs
をスライス(dirs[:]
)にて差し替えます。
root_dir = os.path.abspath(os.path.dirname(__file__)) target_files = [] for root, dirs, files in os.walk(root_dir): dirs[:] = [d for d in dirs if 'hoge' not in os.path.join(root, d)] targets = [os.path.join(root, f) for f in files] target_files.extend(targets) for f in target_files: print(f)
実行してみると、
- path/to/root/os_walk/bar/hoge/ham.txt
- path/to/root/e.g._os_walk/bar/hoge/spam.txt
- path/to/root/e.g._os_walk/foo/hoge/ham.txt
- path/to/root/e.g._os_walk/foo/hoge/spam.txt
が除外されたファイル一覧を取得できました。
path/to/root/e.g._os_walk/make_file.py path/to/root/e.g._os_walk/os_walk.py path/to/root/e.g._os_walk/bar/foo.txt path/to/root/e.g._os_walk/bar/fuga/ham.txt path/to/root/e.g._os_walk/bar/fuga/spam.txt path/to/root/e.g._os_walk/foo/foo.txt
念のため、この時のos.walk()の挙動も確認します。
root_dir = os.path.abspath(os.path.dirname(__file__)) target_files = [] for root, dirs, files in os.walk(root_dir): dirs[:] = [d for d in dirs if 'hoge' not in os.path.join(root, d)] print('-'*10) print('root:{}'.format(root)) print('dirs:{}'.format(dirs)) print('files:{}'.format(files)) targets = [os.path.join(root, f) for f in files] target_files.extend(targets)
実行してみると、hoge
ディレクトリが結果に含まれていませんでした。
---------- root:path/to/root/e.g._os_walk dirs:['bar', 'baz', 'foo'] files:['make_file.py', 'os_walk.py'] ---------- root:path/to/root/e.g._os_walk/bar dirs:['fuga'] files:['foo.txt'] ---------- root:path/to/root/e.g._os_walk/bar/fuga dirs:[] files:['ham.txt', 'spam.txt'] ---------- root:path/to/root/e.g._os_walk/baz dirs:['fuga'] files:[] ---------- root:path/to/root/e.g._os_walk/baz/fuga dirs:[] files:[] ---------- root:path/to/root/e.g._os_walk/foo dirs:[] files:['foo.txt']
remove(dirs.remove('hoge')
)で差し替え
続いて、dirs
をdirs.remove('hoge')
にて差し替えます。
list.remove()は該当するものがない場合は例外を送出することに注意します。
python - Is there a simple way to delete a list element by value? - Stack Overflow
target_files = [] for root, dirs, files in os.walk(root_dir): try: dirs.remove('hoge') except: pass targets = [os.path.join(root, f) for f in files] target_files.extend(targets) for f in target_files: print(f)
実行結果はスライスと同じだったため、省略します。
代入で差し替え (NG)
スライスを使わない代入で差し替えてみます。
target_files = [] for root, dirs, files in os.walk(root_dir): dirs = [d for d in dirs if 'hoge' not in os.path.join(root, d)] print('-'*10) print('root:{}'.format(root)) print('dirs:{}'.format(dirs)) print('files:{}'.format(files)) targets = [os.path.join(root, f) for f in files] target_files.extend(targets) for f in target_files: print(f)
実行するとhoge
ディレクトリが含まれたままでした。
公式ドキュメントのように、スライスなどを使うのが良さそうです。
---------- root:path/to/root/e.g._os_walk dirs:['bar', 'baz', 'foo'] files:['make_file.py', 'os_walk.py'] ---------- root:path/to/root/e.g._os_walk/bar dirs:['fuga'] files:['foo.txt'] ---------- root:path/to/root/e.g._os_walk/bar/fuga dirs:[] files:['ham.txt', 'spam.txt'] ---------- root:path/to/root/e.g._os_walk/bar/hoge dirs:[] files:['ham.txt', 'spam.txt'] ---------- root:path/to/root/e.g._os_walk/baz dirs:['fuga'] files:[] ---------- root:path/to/root/e.g._os_walk/baz/fuga dirs:[] files:[] ---------- root:path/to/root/e.g._os_walk/baz/hoge dirs:[] files:[] ---------- root:path/to/root/e.g._os_walk/foo dirs:[] files:['foo.txt'] ---------- root:path/to/root/e.g._os_walk/foo/hoge dirs:[] files:['ham.txt', 'spam.txt'] path/to/root/e.g._os_walk/make_file.py path/to/root/e.g._os_walk/os_walk.py path/to/root/e.g._os_walk/bar/foo.txt path/to/root/e.g._os_walk/bar/fuga/ham.txt path/to/root/e.g._os_walk/bar/fuga/spam.txt path/to/root/e.g._os_walk/bar/hoge/ham.txt path/to/root/e.g._os_walk/bar/hoge/spam.txt path/to/root/e.g._os_walk/foo/foo.txt path/to/root/e.g._os_walk/foo/hoge/ham.txt path/to/root/e.g._os_walk/foo/hoge/spam.txt
ソースコード
GitHubに上げました。e.g._os_walk
ディレクトリ以下が今回のファイルです。
thinkAmi-sandbox/python_misc_samples