generatorは、ジェネレーターイテレーターと呼ばれる特別な種類のイテレーターを返す関数または式です。
Generator-iteratorsは遅延評価です。valuesをメモリに保存するのではなく、必要になったときに値を_生成_します。
ジェネレーター関数は他の関数と同じように見えますが、1つ以上のyield式を含みます。
各yieldはコードの実行を中断し、現在の実行状態(ローカル変数やtry文をすべて含む)を保存します。
ジェネレーターが再開すると、中断したところから状態を引き継ぎます。呼び出しのたびにリセットされる通常の関数とは異なります。
ジェネレーターは、他のループ関数や再帰関数とよく似た形で作りますが、yield式が必要です。これについては、あとで詳しく見ていきます。
例として、与えられた数値の配列から_平方_を返す関数を考えてみましょう。
今のままでは、値を返す前にすべての入力を処理しなければなりません:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
この関数は、次のようにしてジェネレーターに変換できます:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
これは、すべての値を_一度に_必要としないときにジェネレーターを使う、という考え方に基づいています。
_今処理している_値だけを計算するので、メモリと処理能力を節約できます。
ジェネレーター(厳密にはgenerator-iteratorsです。詳しくは下の注意書きをご覧ください。)はiteratorの一種で、Pythonでiteratorやiterableが期待される場所ならどこでも使えます。
これには、引数としてiterable/iteratorを必要とする_関数_や_オブジェクト_も含まれます。
さらに詳しく知りたい場合は、How to Make an Iterator in Pythonをご覧ください。
ジェネレーターイテレーターは、イテレーターの特別な部分集合です。
Iteratorsは、_イテラブル_を繰り返し処理するための仕組み(プロトコル)です。
ジェネレーターイテレーターと、一般的なPythonのiterablesが返すイテレーターはとてもよく似た動作をしますが、いくつか重要な違いがあります:
indexesを_持ちません_。
足し算や引き算で前後の値を参照することはできませんし、角括弧([])による記法やスライスも使えません。len()関数と組み合わせて使うことはできません。generator-iteratorからすべての値を集めるときは注意してください!squares_generator()ジェネレーターを使うには、次のようにします:
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
ジェネレーター内の値は、next()関数を使って取り出したり参照したりすることもできます。
next()はジェネレーターオブジェクトの__next__()メソッドを呼び出し、ジェネレーターのコードをyield式のところまで「進める」、つまり評価します。すると値が「yield」され、返されます。
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
generatorが最後まで消費され、返す値がなくなると、StopIterationエラーを送出します。
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
ジェネレーターは、_イテレーター_の特別な部分集合です。
Iteratorsは、_イテラブル_を繰り返し処理するための仕組み(プロトコル)です。
ジェネレーターと、一般的なPythonのiterablesが返すイテレーターはとてもよく似た動作をしますが、いくつか重要な違いがあります:
ジェネレーターは_一方通行_です。前の値に「戻る」ことはできません。
ジェネレーターを繰り返し取り出すと、返された値は消費されます。リセットはできません。
ジェネレーターは(_遅延評価される_ため)ソートはできず、逆順にすることもできません。
ジェネレーターはindexesを_持ちません_。そのため、足し算や引き算で前後の値を参照することはできません。
ジェネレーターはlen()関数と組み合わせて使うことはできません。
ジェネレーターは_有限_にも_無限_にもなりえます。_無限_のジェネレーターからすべての値を集めるときは注意してください。
yield式yield式はreturn式とよく似ています。
return式とは_異なり_、yieldは_特定の時点_で呼び出し元に値を渡し、それ以降の値の評価・返却は、要求されるまで中断します。
yieldが評価されると、それを囲む関数の実行を一時停止し、_その時点での_関数の値を返します。
その後も関数は_スコープにとどまり_、__next__()が呼ばれると、再びyieldに到達するまで実行が再開されます。
関数の外でyield式を使うことはできません。
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
ジェネレーターは多くの場面で役立ちます。
大きなコレクションを扱うとき、そのすべての値をmemoryに入れたくないことがあります。
ジェネレーターを使えば、大きなデータを少しずつ処理でき、メモリを節約し、パフォーマンスを向上させられます。
処理や計算が_複雑_だったり、コストが高かったり、_無限_だったりする場合にも、ジェネレーターはとても役立ちます:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
これで、infinite_sequenceオブジェクトに対して__next__()が呼ばれるたびに、直前の数値 + 1が返されます。