Pythonの技法:ジェネレータを用いた遅延リストの構築

文:Nick Gibson(Builder AU)  翻訳校正:原井彰弘
2008-01-17 15:00:00
  • このエントリーをはてなブックマークに追加

 プログラムがやる気を出しすぎて、必要のない処理やあなたが実行して欲しくないと思っている処理まで行ってしまうのはよくあることである。プログラムにはもっと怠け者であって欲しいのだ。そこで、ジェネレータの出番である。Pythonのジェネレータを用いると、いつどれだけ処理を行って欲しいかを正確に指定することが可能となる。

 既にリストの内包表記というものを紹介した。リストの内包表記を用いれば、リストの内容をより自然な方法で表現することが可能なのであった。本稿では、そのリストの内包表記と「いとこ」の関係にあるもの「ジェネレータ」を紹介しよう。ジェネレータを利用すると、シーケンスの各要素を一つずつ構築することが可能となるため、あなたが必要とするだけの処理が行われるようになるのである。

 これは遅延評価と呼ばれ、プログラムが値を実際に必要とするまで、特定の値の計算を遅らせるしくみである。遅延評価を利用すれば、まったく使用されない値を計算してしまうことがなくなってパフォーマンスが向上するので、遅延評価はプログラミングのさまざまな場面で活用できる。HaskellやMirandaのような言語では、遅延評価がデフォルトの振る舞いになっているほどだ。またSchemeやOCaml、Pythonでも、特別な構文を用いれば遅延評価を行える。Pythonの場合は、ジェネレータでこれを実現する。

 ジェネレータを利用して関数を記述すると、ただ単に結果を返すだけではなく、実行中に「一時停止」を行って次に呼び出されたときにはそこから実行を再開することが可能になる。ジェネレータを記述するのに専用の複雑な構文は必要ない。途中の値を返したいときにyield文を用いるだけでよいのである。これは例を用いて説明するのが手っ取り早い。次のコードを見て欲しい。

>>> def generator1():
...     yield "first"
...     yield "second"
...     yield "third"
... 
>>> gen = generator1()
>>> gen

>>> gen.next()
'first'
>>> gen.next()
'second'
>>> gen.next()
'third'
>>> gen.next()
Traceback (most recent call last):
  File "", line 1, in ?
StopIteration
>>> 

 この例ではまず最初に、「first」「second」「third」という3つの値を生み出すジェネレータ「generator1」を定義している。この関数は、新しいジェネレータのオブジェクト(gen)が作成されたときに実行が開始される。そして、ジェネレータのnextメソッドが呼び出されるたびに、次のyieldに達するまで実行が続けられるのである。最後に、ジェネレータの実行がブロックの最後かreturn文にたどり着くと、StopIteration例外がスローされる。

  • 新着記事
  • 特集
  • ブログ