Python のジェネレーターで return した値はどこへ行くのか: StopIteration.value と PEP 479

ジェネレーターの return 値は for 文では受け取れず、StopIteration.value に入ります。yield from での受け取り方、ジェネレーター内の StopIteration が RuntimeError になる理由、close() の戻り値を整理しました。

開発Pythonジェネレーター例外

ジェネレーター関数の中でも 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 ドキュメントで確認しました。例の出力はドキュメントに書かれたルールから導いたもので、この記事のためにコードを実行して得た結果ではありません。

参考資料

ブログへ