Python3 で os.walk() を使ってファイル一覧を生成しようとしてyeildを使うまで

Python3 でツールを書いていて、あるフォルダ以下のファイル一覧を再帰的に取得したくなった。

MS-DOS の 「DIR /S /B」みたいなイメージで。

Python の osモジュールには、os.walkというこういうときに便利な関数があるので、これを使ってファイル一覧を作ることにした。最初に書いたのが以下。

import os
result = []
for path, dirs, files in os.walk(rootDir):
    result += map(lambda x: os.path.join(path, x), files)

まあ、これで十分シンプルでいいんだけど、for文使って足し込むのがイヤだったり、結果がiteratorでなくてリストそのもので作られてしまうので、リスト内包表記を使ってみることにした。

import os
files = [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)]

でもこれじゃ望んだ結果がこない。リストのリスト(のiterator)が返された。失敗失敗。

そこで、reduceを使って、リスト内のリストを結合することにしよう。…と思ったら、Python3からはreduce関数が標準関数でなくなって、functoolsモジュールに移動したらしい。使えないからビックリした。

functoolsモジュールからreduceをimportして、以下の様に書いた。

import os
from functools import reduce
files = reduce(lambda a, b: a+b, [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)])

これは動かなかった。なぜなら、リスト内包表記の方が返すリストが、mapオブジェクトのリストだから。
'+'演算子はmapオブジェクトに対しては使えない。そこで、mapの結果をリストに変換して以下の様に書く

import os
from functools import reduce
files = reduce(lambda a, b: list(a)+list(b), [map(lambda x: os.path.join(path, x), files) for path, dirs, files in os.walk(rootDir)])

これで、filesにrootDir以下のファイルをリストで格納できた。なんか冗長になっちゃった。

しかし、ここに至って、結果がiteratorでなくてリストになってしまっていることに気付いた。大量のファイルを扱う場合であれば、メモリ効率を考えて、できればiteratorで受け取りたいところ…

そこでPythonのドキュメントを探していたら、どうも、yield文というのを使って実現するらしい。
そこで、1行で書くのはあきらめて、yield を使ってみることにした

yeildを使ってファイルの一覧を返すiterator

def filesIterator(rootDir):
    for path, dirs, files in os.walk(rootDir):
        for filePath in map(lambda x: os.path.join(path, x), files):
            yield filePath

これでファイルの一覧をIteratorで取得できるようになったので、あとはfor文なりなんなりで自由に処理することができる。
でもこれ、for文がネストしているのをなんとか出来ないかなあ…