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文がネストしているのをなんとか出来ないかなあ…