Python のジェネレーターで return した値はどこへ行くのか: StopIteration.value と PEP 479
ジェネレーターの return 値は for 文では受け取れず、StopIteration.value に入ります。yield from での受け取り方、ジェネレーター内の StopIteration が RuntimeError になる理由、close() の戻り値を整理しました。
ジェネレーター関数の中でも return 値 は書けます。構文エラーにはならず、警告も出ません。ところが、そのジェネレーターを for 文や list() で回しても、返した値はどこにも出てきません。値が消えたわけではなく、反復の終わりを知らせる StopIteration 例外の中に入っています。
return は StopIteration を送出する
言語リファレンスの return 文の説明によると、ジェネレーター関数での return はジェネレーターの終了を意味し、StopIteration を送出させます。値を返した場合はその値で StopIteration が作られ、StopIteration.value 属性になります。何も渡さなければ value は None です。
def read_items():
yield "a"
yield "b"
return 2 # 読んだ件数
gen = read_items()
print(next(gen)) # a
print(next(gen)) # b
try:
next(gen)
except StopIteration as e:
print(e.value) # 2
値を見るには、自分で next() を呼んで StopIteration を捕まえる必要があります。for 文の仕様が述べているのは、イテレーターが出す要素を順にターゲットへ代入し、イテレーターが尽きたら else 節を実行して終わる、ということだけです。最後の StopIteration が持つ値を受け取る構文はありません。次のループが扱うのは "a" と "b" だけで、2 は捨てられます。
for item in read_items():
print(item)
戻り値の仕組みは Python 3.3 で入りました。組み込み例外のドキュメントによれば、3.3 で value 属性と、ジェネレーター関数がそれを使って値を返す機能が追加されています。
受け取る側は yield from
この値を自然に受け取れるのは、別のジェネレーターから yield from で委譲したときです。リファレンスは、サブイテレーターが終わると送出された StopIteration の value が yield from 式の値になり、サブイテレーターがジェネレーターならその return の値が自動的に入る、と説明しています。
def outer():
count = yield from read_items()
yield f"{count} 件読みました"
print(list(outer())) # ['a', 'b', '2 件読みました']
yield from の役割は値の受け取りだけではありません。サブイテレーターが生成した値は外側のジェネレーターの呼び出し元へそのまま渡り、send() で送った値や throw() で投げた例外も、サブイテレーターが該当するメソッドを持っていればそちらへ転送されます。要素ごとに yield し直すループに書き換えると、この転送と戻り値の両方が失われます。
ジェネレーター内の StopIteration は RuntimeError になる
戻り値とは別に、ジェネレーターの中で StopIteration を扱うときには落とし穴があります。組み込み例外のドキュメントには、ジェネレーターのコードが直接または間接に StopIteration を送出すると RuntimeError に変換され、元の StopIteration は新しい例外の原因(__cause__)として残る、と書かれています。これが PEP 479 です。
よくあるのは間接的なケースです。別のイテレーターに対してデフォルト値なしで next() を呼ぶと、そのイテレーターが空になったところで StopIteration が出ます。
def pairs(it):
it = iter(it)
while True:
first = next(it)
second = next(it)
yield first, second
list(pairs([1, 2, 3, 4]))
PEP 479 より前は、next(it) から漏れた StopIteration は、ジェネレーターの外から見ると普通の反復終了と区別がつきませんでした。このコードは入力が尽きると黙って止まり、バグで生じた StopIteration でも同じように黙って止まっていたわけです。現在のルールでは、入力が尽きた時点で RuntimeError になります。要素数が偶数でも避けられません。5 回目の next(it) で StopIteration が出るからです。
意図して終わらせるなら return を使います。
_END = object()
def pairs(it):
it = iter(it)
while True:
first = next(it, _END)
if first is _END:
return
second = next(it, _END)
if second is _END:
return # 相手のいない最後の要素は捨てる
yield first, second
next() にデフォルト値を渡すと、イテレーターが尽きたときに StopIteration の代わりにその値が返ります。入力に None が含まれうるなら、None より専用の番兵オブジェクトのほうが安全です。try で StopIteration を捕まえて return しても構いません。大事なのは、ジェネレーターの外へ StopIteration を漏らさないことです。同じ理由で、ジェネレーター内で raise StopIteration(値) と書いていた古いコードは return 値 に直す必要があります。
この変換が適用されるのはジェネレーター(とコルーチン)だけです。クラスで実装したイテレーターの __next__() は、今でも StopIteration を送出して終了を知らせます。
| バージョン | 動作 |
|---|---|
| 3.5 | from __future__ import generator_stop を書いたモジュールでのみ変換 |
| 3.7 | すべてのコードでデフォルトで変換(__future__ の表で mandatory) |
Python 3.7 の変更点のドキュメントにも、PEP 479 がすべてのコードで有効になり、コルーチンとジェネレーターの中で直接または間接に送出された StopIteration が RuntimeError に変換される、とあります。3.6 以前向けに書かれたコードを移すと、この違いで動作が変わることがあります。
close() も値を返す(3.13 以降)
close() は、ジェネレーターが停止している位置で GeneratorExit を送出します。ジェネレーターがこれを捕まえて値を return すると、Python 3.13 以降ではその値が close() の戻り値になります。それより前のバージョンでは None でした。
def summer():
total = 0
try:
while True:
total += yield
except GeneratorExit:
return total
acc = summer()
next(acc) # 最初の yield まで進める
acc.send(3)
acc.send(4)
print(acc.close()) # 3.13 以降: 7、それより前: None
先に next(acc) を呼んでいるのは、send() でジェネレーターを開始するときには値を受け取る yield 式がまだないため、引数を None にしなければならないからです。close() のドキュメントには、ほかにもいくつかのケースが書かれています。すでに終了しているか、GeneratorExit を捕まえずにそのまま送出した場合は None を返し、GeneratorExit を受けたあとにまた yield すると RuntimeError になります。それ以外の例外は呼び出し元へ伝播します。
非同期ジェネレーターに戻り値はない
yield を含む async def、つまり非同期ジェネレーターでは、値つきの return は構文エラーです。書けるのは値なしの return だけで、このとき送出されるのは StopAsyncIteration です。同期ジェネレーターの戻り値に頼った設計を非同期に移すなら、結果を最後の yield で出すか、別のオブジェクトに持たせる形に変える必要があります。
まとめ
- ジェネレーターの
return 値はStopIteration.valueに入る。forやlist()からは見えない。 - 受け取るには
yield fromで委譲するか、next()を呼んでStopIterationを自分で捕まえる。 - 3.7 以降、ジェネレーターから漏れた
StopIterationはRuntimeErrorになる。終わらせたいならreturnを使い、別のイテレーターのnext()にはデフォルト値を渡すか例外を捕まえる。 - 3.13 以降、
close()はGeneratorExitの処理中にreturnした値を返す。
例外が見えなくなる別の例としては Java の try-with-resources と suppressed 例外、Python で評価のタイミングが絡む落とし穴としては デフォルト引数に [] を書いてはいけない理由 もあわせてどうぞ。
この記事の動作の説明は、2026 年 10 月 18 日時点の Python 3.15.0 ドキュメントで確認しました。例の出力はドキュメントに書かれたルールから導いたもので、この記事のためにコードを実行して得た結果ではありません。