遅延ジェネレータの使い方。

簡単な例を、イテレータやジェネレータを使って実装してみます。

 for i in range(1, 11, 1): print i

range(1, 11, 1) は 1..10迄の整数のリストを返す。

リストを返す関数を使った場合、その場合は勿論リストが生成されるので、
リストの数によっては大量のメモリを占領したりすることがあります。
そこで、リストを生成せずに、リストの要素が生成される毎に
その要素に対して何かの処理をさせるよう最適化する。

アプローチの一つとして、リストを生成する関数と
リストの要素を処理する関数の2つを作り、リストを生成する関数には、
その要素を渡す関数を引数に渡す方法もあります。

 def my_print(msg): print msg
 def my_range(x, y):
   num = x
   while num < y
     func(num)
     num += 1

 my_range(1, 10, my_func)


Python2.2+ では、for .. in は、内部でイテレータの生成を要求します。
リストだけでなく、辞書やファイルオブジェクト等も渡す事が出来る。
勿論、イテレータオブジェクトをそのまま渡すことも出来ます。


イテレータPythonでの具体的な実装方法は、

  • __iter__() メソッドが イテレータ・クラスのオブジェクトを返す。
  • イテレータ・クラスでは、next() メソッドを実装し、終端で StopIteration例外を投げる。

を満たすものです。

データ構造を持つクラスを書いたとき等、
イテレータも実装しておくと便利。

1..10迄の値を生成するイテレータのカスタムクラスを定義。

 class range_iter:
     def __init__(self, start, stop):
         self.start = start
         self.stop = stop
         self.val = self.start
     def __iter__(self):
         return self
     def next(self):
         if self.val > self.stop:
             raise StopIteration
         else :
             num = self.val
             self.val += 1
             return num

 for i in range_iter(1, 10): print i

__iter__() が self を返しているので、
自分自身のクラスに next() を実装する。

# 後でcode解析してみるけど、
# リストは生成されず、ループ毎に next()が呼ばれているはず。

python2.2以降からジェネレータがサポートされました。
ジェネレータとは、イテレータをさらに抽象化した様なもの?

 from __future__ import generators

 def range_generator(x, y):
     num = x
     while num <= y:
       yield num
       num += 1

 for i in range_generator(1, 10): print i

yield が値を返し、for のブロックを実行し
その次の要素を生成するため、yieldの次から処理が再開されます。
つまり、実際には・・・

 num = x
 while num <= y:
   print num
   num += 1

という処理と等価になる。
関数オブジェクトを引数に渡す方法ににているが、
要素を受け取り処理をする関数を定義する手間が省ける。

実は、
range については、xrangeという便利なビルトイン関数があるので、
この例では、カスタム・イテレータや、ジェネレータを使うメリットは
全然ありません。(^^;イテレータ/ジェネレータの仕組みを知るため再開発

 for i in xrange(1, 11, 1): print i