Python講座 / イテレーターとジェネレーター
イテレーターは値を一つずつ取り出す仕組みで、ジェネレーターはyieldで実行を中断・再開して値を生成するイテレーターを作ります。一度消費した値は自動で戻らないため、繰り返し読むデータでは使い方を決める必要があります。
反復できる値と取り出す状態を区別する
リストなどの反復可能な値へiterを使うとイテレーターを得ます。nextは次の値を取り出し、終了するとStopIterationを送出します。for文はこの終了を扱って反復を終えます。
yieldを含む関数を呼ぶとジェネレーターが作られます。その時点で関数全体が実行されるわけではありません。nextなどによる消費で実行が進み、yieldで値を返して局所変数や実行位置を保持します。
ファイルストリームと連続グループは、collectionsとitertoolsで集計・反復するで扱う規則とつながります。
累計を順に取り出して実行する
generators.pyへ保存し、python generators.pyを実行します。最初の一つをnextで取り出した後に、残りをlistへ集めています。
def running_totals(amounts):
total = 0
for amount in amounts:
total += amount
yield total
values = running_totals([3500, 8000, 2500])
print(next(values))
print(list(values))
print(list(values))
print(list(running_totals([3500, 8000, 2500])))
予想結果は次のとおりです。
3500 [11500, 14000] [] [3500, 11500, 14000]
既に最後まで消費したgenをもう一度listにすると空です。新しいジェネレーターを作ると、初めから計算できます。
遅延評価の利点と資源の寿命を考える
生成結果を最初からすべて保持しないため、大きな流れを順に処理する用途に向きます。ただし元の入力がリストなら、その入力のメモリーは必要です。またlist(gen)で結果全体を集めれば、その分のメモリーを使います。
小さい結果を何度も使うならリストの方が扱いやすい場合があり、遅延評価が常に高速というわけではありません。処理中の例外も消費する時点で発生します。ファイルを読むジェネレーターでは、途中で読むのを止めた場合も考え、呼び出し側のwithなどでファイルの寿命を明確にします。
消費済みの状態を再現する
最初にnextを二回呼ぶよう変更し、その後のlistに残る値が変わることを確認します。空の入力に対する最初のnextではStopIterationになることも確かめます。比較後は元へ戻してください。
二回読む必要があるなら、毎回新しいジェネレーターを作るか、必要な範囲の結果を保存します。イテレーターを引数として渡す関数が、それをどこまで消費するかも契約の一部です。呼び出すだけでは処理が終わらない点を意識すると、空の結果や遅れて起こる例外を調べやすくなります。
listは値を保持し、iter(list)は取り出す現在位置を保持します。一度nextを呼んだ後に残りをlistにしても、取り出し済みの値は戻りません。使い切った反復子を再びlistにすると空ですが、元のリストは残ります。次のジェネレーター式も消費され、必要なときに値を計算します。
全体を iterator.py に保存し、同じ練習フォルダーで python iterator.py を実行します。
values = [1, 2, 3] iterator = iter(values) print(next(iterator)) print(list(iterator)) print(list(iterator)) print(list(values)) expression = (n * 2 for n in values) print(list(expression))
予想結果:
1 [2, 3] [] [1, 2, 3] [2, 4, 6]




