Python 

Pythonでフォルダ構造を可視化しよう!
第3回:os.walk

2024/04/29

Pythonでフォルダ階層の構造を可視化するための方法を解説する本コラム。
今回は os.walk() を使った方法を解説します。

実行環境

os.walk()

os.walk()Python3.3 で追加された、ディレクトリツリーを再帰的に走査することができる関数です。


引数にルートディレクトリのパスを指定することで、ディレクトリ階層を再帰的に走査します。

今回は、以下のようなディレクトリ階層を例に解説していきます。

root
│
├─ topDir_01
│  ├─ file_01.txt
│  └─ file_02.txt
│
└─ topDir_02
   └─ subDir_01
      └─ file_03.txt

実行すると、以下のように (フォルダパス, フォルダ名, ファイル名) の形式の タプル のリストが返ってきます。

import os

path = "C:\\root"

for i in os.walk(path):
    print(i)

# >>>
# ('C:\\root', ['topDir_01', 'topDir_02'], [])
# ('C:\\root\\topDir_01', [], ['file_01.txt', 'file_02.txt'])
# ('C:\\root\\topDir_02', ['subDir_01'], [])
# ('C:\\root\\topDir_02\\subDir_01', [], ['file_03.txt'])

前回の記事 で紹介した glob.glob() に比べ、ディレクトリに関する情報がタプルとして返ってくるため、ディレクトリの解析などの操作の柔軟性が高いのが特徴です。

引数の解説

os.walk(top, topdown=True, onerror=None, followlinks=False)

top

第一引数は、走査するルートディレクトリのパスを指定します。

バージョン3.6以降では、pathlib を使ったオブジェクトなどの path-like オブジェクト を受け入れるようになりました。

topdown=True

オプション引数。True の場合、まずルートディレクトリ直下のディレクトリ・ファイルのタプルを生成し、その後サブディレクトリに潜ってタプルを生成し…というように、トップダウンでリストに展開していきます。
False の場合、一度すべてのサブディレクトリからタプルを生成した後、ボトムアップで走査してリストに展開します。

True の場合は、ループの途中(インプレース)でリストを変更(要素の追加、削除)して結果に反映することができます。
これにより、検索を省略したり、途中で変更したディレクトリ・ファイルを os.walk() に知らせることができます。
しかし、False の場合は、全てのディレクトリからタプルを生成した後リストに展開されるため、途中の変更は結果に反映されません。

onerror=None

オプション引数。例外を送出した際に呼び出す関数を指定することができます。
エラーを報告して処理を継続したり、例外を送出して走査を中断する関数を実装し、ループの途中で呼び出すことが可能です。

followlinks=False

オプション引数。ディレクトリへのシンボリックリンクをたどるかを指定することができます。
デフォルトは False で、例えばショートカットファイルがディレクトリに含まれていたとしても、そのリンク先のパスを走査しません。

True に指定すると、シンボリックリンクがルートディレクトリを指していた場合に、無限ループになることに注意してください。

ファイル一覧を取得する

では、実際に os.walk() を使ったディレクトリ走査の方法を解説します。

まずは、ルートディレクトリ内のファイルの一覧を取得する方法です。

戻り値の (フォルダパス, フォルダ名, ファイル名) のタプルの中から、ファイル名だけを抽出すれば可能です。

# ルートディレクトリ内のファイル一覧を出力
for root, dir, file in os.walk("C:\\root"):
    if len(file): # リストが空でない場合
        print("\n".join(file))

# >>>
# file_01.txt
# file_02.txt
# file_03.txt

ディレクトリ一覧を取得する

続いて、ディレクトリの一覧を取得する方法です。

先ほどと同じく、タプルからディレクトリ名だけを抽出すれば可能です。

# ルートディレクトリ内のディレクトリ一覧を出力
for root, dir, file in os.walk("C:\\root"):
    if len(dir): # リストが空でない場合
        print("\n".join(dir))

# >>>
# topDir_01
# topDir_02
# subDir_01

階層の深さを調べる

ディレクトリの階層の深さを可視化できるか検証してみます。
これは、タプルの ルートディレクトリ 要素のパス区切り文字をカウントすれば可能です。

下記のコードでは検証として、階層の深さに応じてインデントを下げています。

import os

print("C://root")
# ルートディレクトリ内のディレクトリ一覧を出力
for root, dir, file in os.walk("C:\\root"):
    indent = "  " * (root.count(os.sep))

    for d in dir:
        print(indent + d)

    for f in file:
        print(indent + f)

# >>>
# C:root
#   topDir_01
#   topDir_02
#     file_01.txt
#     file_02.txt
#     subDir_01
#       file_03.txt

os.walk() は、ルート直下のサブディレクトリをすべて走査した後、階層を一つ潜りサブディレクトリを走査し… という順序で処理するため、下記を見てわかる通りディレクトリ階層の構造を把握するには不向きです。

[出力結果]               [実際のディレクトリ階層]

root                |   root
  topDir_01         |     topDir_01
  topDir_02         |       file_01.txt
    file_01.txt     |       file_02.txt
    file_02.txt     |     topDir_02
    subDir_01       |       subDir_01
      file_03.txt   |         file_03.txt

globとの比較

ディレクトリの走査順を glob.glob() 比較してみます。

import os
from glob import glob

print("C://root")
for i in glob("C:\\root\\**", recursive=True):
    indent = "  " * (i.count(os.sep))
    print(indent + os.path.basename(i))

# >>>
# C:/root
#   topDir_01
#     file_01.txt
#     file_02.txt
#   topDir_02
#     subDir_01
#       file_03.txt

出力結果を見て分かるように、ディレクトリの階層構造を可視化するには glob() を使うのが適切なようです。

まとめ

今回は、os.walk() の使い方について解説しました。
本コラムの目的であるディレクトリ階層を可視化するには、glob.glob() を使用するのが、走査する順序的に便利です。

os.walk() は、ディレクトリのまとまった情報がタプルとして返ってくる利点を活かした用途では使い道がありそうです。