イテレータとジェネレータ
まず別々の反復子と共有した反復子を比較します。続いてyieldで停止する関数を作り、next、send、closeで再開・終了させながら、保持されている値と処理の順序を確認します。
説明
リストは繰り返し参照できるデータですが、そこから作る反復子は「次にどこを読むか」という進行位置を持ちます。nextを呼ぶと一つ進み、終端ではStopIterationが発生します。
まず別々の反復子と共有した反復子を比較します。続いてyieldで停止する関数を作り、next、send、closeで再開・終了させながら、保持されている値と処理の順序を確認します。
1別々の位置と共有した位置
同じリストから二つの反復子を作ります。aliasはaと同じ反復子への別名です。
values = [10, 20, 30]; a = iter(values); b = iter(values)別々の反復子を作る; alias = a一つを別名で共有; print(next(a), next(alias), next(b), next(a)それぞれの次の値); print(iter(a) is a反復子自身が返るか確認)aとaliasは同じ位置を共有するので10、20、30と進みます。bは別の位置を持つため10から始まります。反復子自身へiterを呼んでも同じ対象が返り、位置は初期化されません。
2終端の通知と既定値
aは全要素を読み終えています。通常のnextと、既定値を指定したnextを試します。
try:
next(a)終端で次の値を要求
except StopIteration終端の通知を受け取る:
print("StopIteration")
print(next(a, "end")終端で返す既定値, next(a, "end"))通常のnextはStopIterationを送出します。既定値を指定した場合はendが返りますが、位置が先頭へ戻るわけではありません。再び同じ反復子を呼んでも終端です。
3値を受け取って再開するジェネレータ
yieldで合計を返して停止し、再開時に受け取った値を加える関数を定義します。Noneを受け取った場合は1を加える仕様です。
def accumulator():停止・再開できる関数
total = 0
try:
while True:
delta = yield total合計を返し、再開時の値を受け取る
total += 1 if delta is None else delta受け取った値を反映
finally:終了時の処理
print("closed")yieldを含む関数を呼ぶと、実行を停止・再開できるジェネレータが作られます。delta = yield totalは、totalを外へ返して停止し、次にsendされた値をdeltaへ受け取る式です。finallyは終了時の後処理です。
4呼んだだけでは本体は進まない
ジェネレータを作り、実行状態を取得します。
import inspect実行状態の取得機能; g = accumulator()ジェネレータを作る; print(inspect.getgeneratorstate(g)現在の状態)GEN_CREATEDは、まだ本体の実行を始めていない状態です。関数呼出しの時点でwhileを回し続けるわけではありません。
5最初のyieldまで進める
nextで開始し、停止後の状態と局所変数totalを確認します。
print(next(g)最初のyieldまで実行); print(inspect.getgeneratorstate(g)停止状態, g.gi_frame.f_locals["total"]保持された局所変数)最初のyieldで0を返し、GEN_SUSPENDEDになります。totalは0のまま保持されています。deltaへの代入は、次に再開したときに完了します。
6sendとnextで再開
sendで3を渡し、その後に通常のnextで進めます。
print(g.send(3)停止中のyieldへ3を渡す); print(next(g)Noneを渡して再開); print(g.gi_frame.f_locals["total"]二回の更新後の合計)send(3)はdeltaへ3を渡すので合計3を返します。nextはNoneを渡して再開するため、この関数では1が加わり4になります。どちらも次のyieldまで実行して再び停止します。
7途中で明示的に終了する
closeで終了し、後処理と最終状態を確認します。
g.close()停止中の処理を終了; print(inspect.getgeneratorstate(g), g.gi_frame終了状態とフレーム); print(next(g, "end")終了後の値要求)finallyのclosedが表示され、状態はGEN_CLOSED、実行フレームはNoneになります。終了したジェネレータをnextで再開することはできず、ここでは既定値endが返ります。
8有限個の値と終了時の戻り値
二つの値をyieldした後、return 2で終了する関数を定義します。
def finite():有限のジェネレータ
yield 0一つ目の要素
yield 1二つ目の要素
return 2終了通知に渡す値yieldの0と1は反復で取り出す要素です。returnの2は三つ目の要素ではなく、終了を示すStopIterationのvalueとして渡ります。
9通常の反復で受け取る要素
listで反復した結果と、個別に進めるための新しい反復子を用意します。
print(list(finite())通常の反復で全要素を取得); f = finite()独立した反復子を作る; print(next(f), next(f)二つのyieldまで進める)listに含まれるのは0と1だけです。fも二つのyieldを通過しています。次のnextでreturnへ到達します。
10終了通知の情報
三回目のnextで終了を受け取り、valueを表示します。
try:
next(f)returnまで進める
except StopIteration as error終了通知を受け取る:
print(type(error).__name__, error.valuereturnに指定した値)StopIterationのvalueは2です。forやlistはこの通知を反復の終了として扱うため、この値を通常の要素として追加しません。
11終端ではない例外
一つの値を返した後で、処理上のエラーになる関数を作ります。
def broken():
yield 1最初の要素を返す
raise ValueError("invalid record")処理中のエラーValueErrorは反復の正常終了を示す通知ではありません。forがこの例外まで自動的に正常終了へ変えるわけではありません。
12forでも処理中のエラーは伝わる
forで消費し、ValueErrorを外側で受け取ります。
try:
for value in broken():反復を進める
print(value)
except ValueError as error処理上の失敗を受け取る:
print(type(error).__name__, str(error))1を表示した後でValueErrorが伝わります。終端のStopIterationと、処理に失敗した例外は役割が異なります。
まとめ
反復子は進行位置を保持します。別名で共有すると、どちらから読んでも同じ位置が進みます。
yieldは局所状態を残して停止し、nextやsendで再開します。closeは終了時の後処理を実行します。
StopIterationは終端の通知です。他の例外を正常終了と混同せず扱います。
操作履歴と解説を振り返れます。
Python 3
values = [10, 20, 30]; a = iter(values); b = iter(values); alias = a; print(next(a), next(alias), next(b), next(a)); print(iter(a) is a)10 20 10 30 True
try:
next(a)
except StopIteration:
print("StopIteration")
print(next(a, "end"), next(a, "end"))StopIteration end end
def accumulator():
total = 0
try:
while True:
delta = yield total
total += 1 if delta is None else delta
finally:
print("closed")import inspect; g = accumulator(); print(inspect.getgeneratorstate(g))GEN_CREATED
print(next(g)); print(inspect.getgeneratorstate(g), g.gi_frame.f_locals["total"])0 GEN_SUSPENDED 0
print(g.send(3)); print(next(g)); print(g.gi_frame.f_locals["total"])3 4 4
g.close(); print(inspect.getgeneratorstate(g), g.gi_frame); print(next(g, "end"))closed GEN_CLOSED None end
def finite():
yield 0
yield 1
return 2print(list(finite())); f = finite(); print(next(f), next(f))[0, 1] 0 1
try:
next(f)
except StopIteration as error:
print(type(error).__name__, error.value)StopIteration 2
def broken():
yield 1
raise ValueError("invalid record")try:
for value in broken():
print(value)
except ValueError as error:
print(type(error).__name__, str(error))1 ValueError invalid record
演習完了