型についてのあれこれ(7) - イテレータとジェネレータ

初回公開:
最終更新:2017/07/09

【 目次 】

イテレータ

イテレータについては、私が下手な解説を加えるより次の公式ドキュメントを参照されれば充分であろう。

しいて説明をくわえれば、

イテレータ - Wikipedia

イテレータ(英語: Iterator)とは、プログラミング言語において配列やそれに類似するデータ構造の各要素に対する繰返し処理の抽象化である。

リストや辞書などのコレクションはデータの集まりであるが、 イテレータはコレクションからデータを繰り返し1個ずつ値を取り出すためのオブジェクトという事になる。

イテレータ型の定義は

  • 5.5. イテレータ型
  • 9.8. イテレータ (iterator)

    イテレータプロトコルの背後にあるメカニズムを一度目にすれば、自作のクラスにイテレータとしての振る舞いを追加するのは簡単です。 iter() メソッドを定義して、 next() メソッドを持つオブジェクトを返すようにしてください。

クラスがイテレータをサポートするには__iter__メソッドとnext()メソッドの実装が必要という事になる。

Iteratorの実装例は

イテレータはインターフェース
JavaやC#プログラマーからみると、イテレータはイテレータインターフェースを実装したクラスと考える事ができる。

javaのイテレータは

イテレータインターフェースを実装する事によりfor文を使ってコレクションから1個ずつ値を取り出す事が可能になる。
型についてのあれこれ(8) - コレクションとfor文を参照

ジェネレータ

  • ジェネレータ と コルーチン - みねこあ

    ジェネレータ(Generator)とは、イテレータコンパチなインターフェイスを持つけど、 指すべきコレクションがあるわけでもなく、 そのたんびに 値を作り出して返すようなモノを作るモノをいいます。

ジェネレータは、イテレータがコレクションから値を生成(ジェネレート)する時に、コレクションの要素をすべて同時に処理するのではなく、yield文を使って必要な時に都度処理をおこなうイテレータオブジェクトの事を言うようだ。

つまり、ジェネレータは関数という事になる。

yield文については

0からN-1の値を返すジェネレータ関数は、以下のような関数として定義できる。

def generate_ints(N):
    for i in range(N):
        yield i

そして、この関数よりジェネレータオブジェクトを取得する事ができる。

>>> gen = generate_ints(3)
>>> gen
<generator object generate_ints at ...>

このジェネレータオブジェクトのnextメソッドを使って値を1個ずつ値を取り出す事ができるようになる。
最初にnextメソッドが呼ばれると、generate_ints関数の実行されyieldを実行した時点で処理が中断されyieldのパラメータiがnextメソッドの戻り値として戻ってくる事になる。

>>> gen.next()
0

次のnextメソッドが実行されるとgenerate_ints関数の処理が再開されて、次のyieldにたどりつくまで実行され再び中断、次のyieldの値が戻ってくる事になる。

>>> gen.next()
1

そして、最後の値まで達すると

>>> gen.next()
Traceback (most recent call last):
  File "stdin", line 1, in ?
  File "stdin", line 2, in generate_ints
StopIteration

StopIterationが発生する。
これらの動作により、ジェネレータ関数はfor文を使ってイテレータとして利用できることになる。

for i in generate_ints(5):
    print i
  • 9.9. ジェネレータ (generator)

    ジェネレータを使ってできることは、前節で記述したクラスベースのイテレータを使ってもできます。
    ジェネレータを使うとコンパクトに記述できるのは、 iter() と next() メソッドが自動的に作成されるからです。

話はそれるが、yield文はコルーチンという事もできるかな。

  • コルーチン - Wikipedia

    コルーチン(英: co-routine)とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。

ジェネレータにはジェネレータ式というのがあって、 リストの内包表記の角括弧の代わりに丸括弧で囲むとジェネレータとして動作してくれるみたい。

以下も参考に

公式ドキュメントより関連がありそうなものを

iterable
(反復可能オブジェクト) 要素を一つずつ返せるオブジェクトです。
反復可能オブジェクトの例には、(list, str, tuple といった) 全てのシーケンス型や、 dict や file といった幾つかの非シーケンス型、あるいは __iter__() か __getitem__() メソッドを実装したクラスのインスタンスが含まれます。 反復可能オブジェクトは for ループ内やその他多くのシーケンス (訳注: ここでのシーケンスとは、シーケンス型ではなくただの列という意味)が必要となる状況 (zip(), map(), ...) で利用できます。
反復可能オブジェクトを組み込み関数 iter() の引数として渡すと、オブジェクトに対するイテレータを返します。このイテレータは一連の値を引き渡す際に便利です。反復可能オブジェクトを使う際には、通常 iter() を呼んだり、イテレータオブジェクトを自分で扱う必要はありません。 for 文ではこの操作を自動的に行い、無名の変数を作成してループの間イテレータを記憶します。イテレータ(iterator) シーケンス(sequence), およびジェネレータ(generator)も参照してください。
iterator
一連のデータ列 (stream) を表現するオブジェクトです。イテレータの next() メソッドを繰り返し呼び出すと、データ列中の要素を一つずつ返します。後続のデータがなくなると、データの代わりに StopIteration 例外を送出します。その時点で、イテレータオブジェクトは全てのオブジェクトを出し尽くしており、それ以降は next() を何度呼んでも StopIteration を送出します。イテレータは、そのイテレータオブジェクト自体を返す __iter__() メソッドを実装しなければならなくなっており、そのため全てのイテレータは他の反復可能オブジェクトを受理できるほとんどの場所で利用できます。
著しい例外は複数の反復を行うようなコードです。 (list のような) コンテナオブジェクトでは、 iter() 関数にオブジェクトを渡したり、 for ループ内で使うたびに、新たな未使用のイテレータを生成します。このイテレータをさらに別の場所でイテレータとして使おうとすると、前回のイテレーションパスで使用された同じイテレータオブジェクトを返すため、空のコンテナのように見えます。
より詳細な情報は イテレータ型 にあります。
generator
(ジェネレータ) イテレータを返す関数です。通常の関数に似ていますが、 return 文を使わず、代わりに for ループで使ったり next() 関数で1つずつ取り出せる値の列を生成するために yield 文を使います。 yield 文に到達するたびに関数の実行は実行状態(ローカル変数や実行中の try 文などを含む)を保存して中断されます。ジェネレータが再開されるとき、(通常の関数が実行の度に初期状態から開始するのに対して)中断した状態から実行を開始します。

itertools モジュール

標準モジュールitertoolsに含まれるよく使うイテレータや、イテレータ同士の連結に使う関数を利用する事ができる。

ページのトップへ戻る