イテレータ

イテレータは、複数個の要素を保持しているオブジェクトから、要素を一つずつ順に取り出すためのインタフェースである。複数個の要素を保持している型として、リスト、ディクショナリ、タプル、集合などがある。これらのオブジェクト(例えば、リスト)に対して、for 構文を適用すると、リスト中の各要素を順に取り出すことができる。これは、リストがイテレータとしての機能が実装されているために、for 構文による要素の取り出しが可能になるからである。

for 構文を利用せずに、イテレータの基本的な制御方法でリストの要素を取り出す例を次に示す。はじめに、リストを iter 関数に代入し、リストをイテレータとして制御できるような形に変換する。次に、next 関数を使って、リスト中の最初の要素を取り出す。その後、next をもう一度使って、2 番目の要素を取り出す。このように、iter 関数でリストをイテレータに変換すると、next 関数を必要な回数だけ実行して、要素を 1 つずつ順に取り出すことができるようになる。そして、最後の要素に達したあとに、next 関数を実行すると、StopIteration 例外が発生する。

a = [1, 2, 3, 4, 5]
a_iter = iter(a)

next(a_iter)
# 1

next(a_iter)
# 2

next(a_iter)
# 3

next(a_iter)
# 4

next(a_iter)
# 5

next(a_iter)
# StopIteration

リストに対して for 構文を適用すると、リストの中から要素を 1 つずつ順に取り出すことができる。これは、for 構文を使用したときに、自動的に iter 関数が呼び出されて、その後、next 関数を繰り返して実行して、StopIteration となったら for 構文から抜ける、という制御が書かれていると考えても良い。

イテレータを自作することができる。イテレータをクラスとして定義し、クラスの中で __iter__ および __next__ メソッドを定義する。iter 関数を使用したときに __iter__ が実行され、next 関数を使用したときに __next__ が実行される。次は、5 つの値を受け取り、それらをイテレータとして使えるような形で変換している例である。

class Arr:
    def __init__(self, a, b, c, d, e):
        self.x = [a, b, c, d, e]
        self.i = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.i == len(self.x):
            raise StopIteration()
        
        ret = self.x[self.i]
        self.i = self.i + 1
        return ret

a = Arr(1, 2, 3, 4, 5)
for i in a:
    print(i)

# 1
# 2
# 3
# 4
# 5

もう一つの例を示す。与えられたリストの中から、奇数番目の要素を取り出すイテレータの例である。

class EvenList:
    def __init__(self, arr):
        self.x = arr
        self.i = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        current_i = self.i
        
        # i に 2 を足して、次の奇数番目要素のインデックスを取得する
        self.i = self.i + 2
        
        if current_i == len(self.x):
            raise StopIteration()
    
        return self.x[current_i]


a = EvenList([1, 2, 3, 4, 5, 6, 7, 8])

for i in a:
    print(i)

# 1
# 3
# 5
# 7