Iterator

はじめに

ここでは、イテラブルとイテレータとは何か、そして両者の関係について述べます。 また、その知識を前提として、Python における for 文の仕組みについて説明します。

イテラブル

イテラブル (iterable) は、英語としては、「繰り返し可能なもの」、「反復可能なもの」という意味の名詞です。 ここで「繰り返す」、「反復する」とは、Python の文脈では、値を連続的に取得し利用することを指します。 具体的には、

などは、いずれも値を連続的に取得して何らかの処理を実行しているといえるでしょう。 これらの処理の裏では、イテラブルが利用されています。 つまり、Python におけるイテラブルとは、for ループなどで繰り返し処理をおこなうことが可能なオブジェクトを意味するのです。

たとえば、list はイテラブルの一つです。実際、

for i in [1, 2, 3]:
    print(i)

のように、list オブジェクトの各要素に対して for ループで走査することが可能です。 strtuplesetdict や file オブジェクトも、for ループを使用できることからイテラブルであることがわかります。

より厳密な定義は次のようになります。 すなわち、

をイテラブルと呼びます。 __iter__ メソッドは、後述するように、イテラブルからイテレータを取り出すためのメソッドです。

上の定義から、あるオブジェクトがイテラブルかどうかを確認するためには、 __iter__ メソッドが定義されているかどうかを調べればよいことがわかります。 この事実から、

>>> is_iterable = lambda x: hasattr(x, "__iter__")
>>> all(map(is_iterable, [list(), str(), tuple(), dict(), set()]))
True

のように、liststrtupledictset がいずれもイテラブルであることを確認することができます。逆に、

>>> is_iterable(1)
False
>>> is_iterable(3.14)
False
>>> is_iterable(lambda x: x)
False

から、整数や浮動小数点数、関数などはイテラブルではありません。

イテレータ

イテレータ (iterator) は、組み込み関数である iter をイテラブルに対して適用することで得られるオブジェクトです。 iter は、適用対象の __iter__ メソッドを呼び出し、イテレータを返します。 つまり、イテラブルとイテレータの関係は次のようになっています:

iter(イテラブル) -> イテレータ

たとえば、イテラブルの一種である list オブジェクトに対して iter を適用すると、 list_iterator というイテレータが生成されます:

>>> iter([1, 2, 3])
<list_iterator object at 0x10c6a73d0>

イテラブルとイテレータの関係が理解できたところで、次に、イテレータの役割について考えましょう。

イテレータは、連続するデータの流れを表します。 言い換えれば、イテレータは、データの集まりに順序という構造を与え、そこから値を一つずつ取り出すという機能をもちます。

実際にイテレータからデータを取得するためには、組み込み関数である next を使用します。 next は、イテレータの __next__ メソッドを呼び出します。 next の役割はシンプルです:

たとえば、上で生成した list_iterator に対して、next を適用すると次のようになります:

>>> i = iter([1, 2, 3])
>>> next(i)
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

このように、イテレータは、イテラブルの値を next 関数により取り出す機能をもちます。 すなわち、イテレータとは

と定義できます。 __next__ メソッドの内部では、次の値の計算、計算結果の返却、StopIteration 例外の送出などがおこなわれます。 イテレータは、イテラブルというデータ構造に対して、そこから値を取り出していくためのロジックを付与したオブジェクトであるといえるでしょう。

なお、任意のイテレータはイテラブルです。 言い換えれば、イテレータに iter 関数を適用してイテレータを取得することが可能です。 これは、イテラブルを使用可能な場面でイテレータも使用することができるようにするためです。 あるイテレータに iter を適用すると、同一のイテレータが返ります。 これらの事実をコードで説明すると、次のようになります:

>>> i = iter([1, 2, 3])
>>> hasattr(i, "__iter__") # イテレータはイテラブルである
True
>>> i is iter(i) # iterを適用すると同一のイテレータが返る
True

このことから、イテレータはイテラブルの特殊な形式であるということができます。

イテレータに関する補足

ここで、イテレータの使用に関して少し補足します。

まず、next の適用により StopIteration 例外を送出するようになったイテレータから、もう一度値を取り出すことはできません。 値が再度必要である場合には、原則としてイテレータを再生成する必要があります。

また、値を __next__ メソッドにより都度計算するという性質上、イテレータに対して len 関数を適用することはできません:

>>> it = iter([1, 2, 3])
>>> len(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'list_iterator' has no len()

なお、これらの事実は、イテレータの一種であるジェネレータなどでも同様に当てはまります。

for 文

これまでの議論から、イテラブルからイテレータが生成されること、そして、イテレータに定義された __next__ メソッドを next 関数を利用して呼び出すことで、 値をイテラブルから連続的に取り出せることがわかりました。 続いてここでは、for 文の実際の動作を、これまでの知識をもとに説明していきます。

上にも書いた、次の for 文について考えましょう:

for i in [1, 2, 3]:
    print(i)

このループを、イテラブルとイテレータを用いて表現すると次のようになります:

iterator = iter([1, 2, 3])
while True:
    try:
        i = next(iterator)
    except StopIteration:
        break
    else:
        print(i)

まず、イテラブルであるリスト [1, 2, 3] に対して iter 関数を適用し、イテレータを取り出しています。 次に、このイテレータに対して next 関数を適用し、イテレータから値を取得します。 最後に、取得した値を print 関数により標準出力へと出力します。 取得できる値がなくなると、next 関数は StopIteration 例外を送出します。 この例外をキャッチすると、break によりループを抜けます。

以上が、for 文実行時における実際の処理の流れとなります。 このように、for 文による繰り返し処理の裏側では、イテラブルとイテレータが利用されているのです。 for 文は、こうしたイテレータの生成や StopIteration 例外の発生時におけるループからの脱出などを自動でおこなってくれる、 一種の糖衣構文であるといえるでしょう。

実装例

最後に、イテラブルとイテレータの実装例を見ていきます。

イテラブルとは、__iter__ メソッドを定義したクラスのオブジェクトのことでした。 そして、__iter__ メソッドの役割は、対応するイテレータを返すことでした。 さらに、イテレータは、次の値を取得するための __next__ メソッドと自分自身を返す __iter__ メソッドが定義されたクラスのオブジェクトのことでした。 これらを念頭に、0 から与えられた整数まで順番に整数を返すイテレータを実装してみます:

class Counter:
    def __init__(self, end):
        self.end = end

    def __iter__(self):
        return CounterIterator(self)


class CounterIterator:
    def __init__(self, counter):
        self.n = 0
        self.counter = counter

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > self.counter.end:
            raise StopIteration
        n, self.n = self.n, self.n + 1
        return n


if __name__ == "__main__":
    for i in Counter(10):
        print(i)

Counter クラスには、__iter__ メソッドが定義されています。 よって、このクラスのオブジェクトはイテラブルです。 また、CounterIterator クラスには、__next____iter__ メソッドが定義されています。 よって、このクラスのオブジェクトはイテレータです。

Counter クラスの __iter__ メソッドは、CounterIterator クラスのオブジェクトを返しています。 つまり、イテレータを返しています。 CounterIterator クラスの __next__ メソッドは、次の値を計算しそれを return します。 また、終了条件が満たされる場合には StopIteration 例外を raise します。

最後の二行に関しては、まず、Counter(10) によりイテラブル e が生成されます。 このイテラブルが for 文の文脈において使用されることにより、CounterIterator が生成されます。 そこから next 関数により値が取り出され、i に代入されます。 こうして i には次々と値が代入されていきますが、__next__ メソッドの実装から、self.n の値が 11 となると StopIterationraise されることがわかります。 このタイミングで、for 文の処理は終了します。

このプログラムの実行結果は次のようになります:

$ python it.py
0
1
2
3
4
5
6
7
8
9
10

このように、イテラブルとイテレータの仕様を満たすようにクラスを定義することで、そのオブジェクトに対して繰り返し処理を実行することができます。

なお、ここではイテラブルとイテレータを概念上区別するために上のように実装しましたが、任意のイテレータはイテラブルであるという事実を考慮すれば、実際はイテレータを一つ実装するだけで事足ります:

class Counter:
    def __init__(self, end):
        self.end = end
        self.n = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > self.end:
            raise StopIteration
        n, self.n = self.n, self.n + 1
        return n


if __name__ == "__main__":
    for i in Counter(10):
        print(i)

まとめ

参考