トラック
/
Python
Python
/
シラバス
/
ジェネレーター
ジェ

ジェネレーター の Python

1個の演習

ジェネレーターについて

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をご覧ください。

Note

ジェネレーターイテレーターは、イテレーターの特別な部分集合です。 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に到達するまで実行が再開されます。

Note

関数の外で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が返されます。

GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

ジェネレーターを学習する

練習はロックされています

ジェネレーターを練習するには、あと2個の演習のロックを解除してください