クラスの超基礎3.イテレータ

イテレートとはいわゆるfor文によるループ処理のことです。このエントリは自作のクラスでfor文によるループ処理を可能にする方法について、です。

まずイテレータの仕組みなのですがこちらに詳しく解説されています。

>>> s = 'abc'
>>> it = iter(s)
>>> it

>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()

Traceback (most recent call last):
File "", line 1, in ?
it.next()
StopIteration

http://www.python.jp/doc/release/tutorial/classes.html#iterator

要約すると、for文はまず与えられたオブジェクトのiter()メソッドを呼び出す。このときオブジェクトにiterメソッドが定義されていない場合は"TypeError: iteration over non-sequence"という例外になります。次にiterメソッドが返したオブジェクトのnext()メソッドを実行して自動的に繰り返す。そしてnext()メソッド内で"StopIteration"という例外が発生したら繰り返しをやめる。
という一連の流れになっているようです。
iter()が返すオブジェクトはnext()メソッドが定義されているほかのオブジェクトでも良いようですが、今回はクラス内に定義することにします。

以上をふまえて、for文にのみ対応する(他のメソッドが何もない)リストもどきを書いてみました。

class Test:
  
  def __init__(self,*args):
    self.index = 0
    self.attrList = args #attrListに受け取った引数をリストとしていれておく
  
  def __iter__(self):
    return self          #ここではnext()をクラス自体に定義しているのでselfを返す
  
  def next(self):
    if self.index == len(self.attrList):
      raise StopIteration           #attrListの最後までループしたらStopIteration例外を発生させる
    
    self.index += 1
    return self.attrList[self.index - 1]
 
a = Test(3,5,7,10)

for v in a:
  print v
#3
#5
#7
#10

関数では"*"を付けた仮引数は実引数のリストを受け取ることができましたがクラスでも使えるようです。以前のエントリPythonのfor in文はJavascriptのfor文に比べると処理の流れが目に見えないので分かりづらいというようなことを書いてしまいましたが、処理の仕組み自体を自分で定義できるという意味ではPythonの方が自由度が高いという気がします。Javascriptのfor in文は原則オブジェクトのすべてのプロパティが列挙されますが、Pythonでは列挙する内容も自分で定義できるのでnext()をきちんと書いてしまえばfor in文はむしろ簡潔に書けるのかもしれません。。