YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

ジェネレータ

またまたPythonでスクリプトを書いていたときにいままで意識してなかったことに出くわしたのでメモ。
os.walk関数を使って指定したディレクトリ以下のファイル、ディレクトリを表示するようなスクリプトを書いていた際に、os.walkの挙動が気になったので調べてみた。

>>> tree = os.walk(".")
>>> type(tree)
<type 'generator'>
>>> print tree
<generator object at 0x79ee0>
>>> tree.next()
('.', ['foo'], ['hoge.py', 'fuga.py'])
>>> tree.next()
('./foo', [], [])
>>> tree.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

なるほどなるほど。まず最初。os.walkは返り値としてgenerator型を返してきた。コレはいったい何なのかと調べてみる。

ジェネレータというのは引用すると下記のことらしい。

ジェネレータは、イテレータを作成するための簡潔で強力なツールです。ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには yield 文を使います。 next() が呼び出されるたびに、ジェネレータは以前に中断した処理を再開します (ジェネレータは、全てのデータ値と最後にどの文が実行されたかを記憶しています)。

関数のようでいて関数でない。これはどういうことか。説明にある。yieldが発行されたらそこで処理を停止して、next()が呼ばれるまで待ってるということらしい。通常の関数ならブロックの最後まで行ってしまうけど、ジェネレータは停止してyeildの値を返し、さらにそれ自身は次の状態を指すイテレータ(ジェネレータイテレータ)になるということのようです。
またforで用いた場合には明示的にnext()を呼ばなくても随時処理を進めてくれるとのこと。すばらしい。たとえばこういうイディオムなんかよく見ます。

for root, dirs, files in os.walk(path):
    for f in files:
        full = os.path.join(root, f)
        print full

イテレータC++とかでも良く出てくるので感覚としては納得で、要はリストの要素を指すポインタみたいなものだと。で、ジェネレータはそれを関数の中の状態に使っていると。まあ個人的にはこんな納得の仕方でとりあえずすすめます。
さらに最後、次の状態が無くなったらStopIterationを発行して終了するということのようです。これはなかなか面白いですね。

■参考