一覧PG-16-1 · 反復子と遅延評価

イテレータとジェネレータ

まず別々の反復子と共有した反復子を比較します。続いて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

1
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
2
try: next(a) except StopIteration: print("StopIteration") print(next(a, "end"), next(a, "end"))
StopIteration
end end
3
def accumulator(): total = 0 try: while True: delta = yield total total += 1 if delta is None else delta finally: print("closed")
4
import inspect; g = accumulator(); print(inspect.getgeneratorstate(g))
GEN_CREATED
5
print(next(g)); print(inspect.getgeneratorstate(g), g.gi_frame.f_locals["total"])
0
GEN_SUSPENDED 0
6
print(g.send(3)); print(next(g)); print(g.gi_frame.f_locals["total"])
3
4
4
7
g.close(); print(inspect.getgeneratorstate(g), g.gi_frame); print(next(g, "end"))
closed
GEN_CLOSED None
end
8
def finite(): yield 0 yield 1 return 2
9
print(list(finite())); f = finite(); print(next(f), next(f))
[0, 1]
0 1
10
try: next(f) except StopIteration as error: print(type(error).__name__, error.value)
StopIteration 2
11
def broken(): yield 1 raise ValueError("invalid record")
12
try: for value in broken(): print(value) except ValueError as error: print(type(error).__name__, str(error))
1
ValueError invalid record

演習完了