Pythonのforループは、Iterableなオブジェクトに対して実行します。
1 2 | for 一時変数 in イテレートできるオブジェクト: ループ内の処理 |
Iterableかどうかは、どのように判断されるのでしょうか?Javaでは、Iterableインターフェースを実装しているかどうかで判定されますが、Pythonにはこういった仕組みはありません。オブジェクトが繰り返し処理(Iterate)に対応したメソッドをもっていればそれでよいのです。こういう仕組みをダック・タイピングといいます。
要するに、Pythonで、Iterableにするためには以下の2つのメソッドが実装されていればよいのです。
- next()
次の要素を返す。次の要素が無ければStopIteration例外を投げる。 - __iter__()
自分自身を返す
カウントダウン反復子
試しに、カウントダウンを行うイテレータを定義してみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >>> class CountDownIter(object): ... def __init__(self, start): ... self.i = start ... def __iter__(self): ... return self ... def next(self): ... if self.i <= 0: ... raise StopIteration ... self.i = self.i - 1 ... return self.i ... >>> for i in CountDownIter(10): ... i ... 9 8 7 6 5 4 3 2 1 0 |
これぐらいだと、イテレータを自作するメリットはありませんね。以下のように記述したほうが分かりやすいし楽です。
1 2 3 4 5 6 7 8 9 10 11 12 | >>> for i in reversed(range(10)): ... i ... 9 (略) 0 >>> for i in range(10)[::-1]: ... i ... 9 (略) 0 |
フィボナッチ数列イテレータ
指定した数までのフィボナッチ数列を返すイテレータを作ってみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | >>> class FibIter(object): ... def __init__(self, limit): ... self.i, self.j = 0, 1 ... self.limit = limit ... def __iter__(self): ... return self ... def next(self): ... self.i, self.j = self.j, self.i + self.j ... if self.i > self.limit: ... raise StopIteration ... return self.i ... >>> >>> for i in FibIter(10): ... i ... 1 1 2 3 5 8 >>> [i for i in FibIter(20)] [1, 1, 2, 3, 5, 8, 13] >>> list(FibIter(20)) [1, 1, 2, 3, 5, 8, 13] >>> FibIter(20) <__main__.FibIter object at 0x0000000002291860> >>> print FibIter(20) <__main__.FibIter object at 0x0000000002291860> |
forループの対象として指定できるだけでなく、リスト内包表記で使ったり、list関数を使ってリストに変換したりできる。ただし、実体はIteratorなので、printで出力した場合はリストとして出力されない。
まとめ
__iter__メソッドと、nextメソッドさえ実装すればイテレータになり、forループやリスト内包表記の対象として指定できる。