クラッシュを止める:Python 3.7以降での「RuntimeError: generator raised StopIteration」の修正

intermediate🐍 Python2026-04-03| Python 3.7 以降(3.10、3.11、3.12、3.13 を含む)、Linux、Windows、または macOS 上で動作するもの。

Error Message

RuntimeError: generator raised StopIteration
#python#generator#stopiteration#pep479#runtime-error

TL;DR: クイック修正

モダンな Python バージョンに移行した後にコードが壊れた場合、通常は構文を少し変更するだけで修正できます。主に2つの選択肢があります。

  • raise StopIterationreturn に置き換える: 現代の Python では、ジェネレータの終了を知らせる正しい方法は return です。
  • next() 呼び出しを保護する: ジェネレータ内で next(iterator) を使用している場合、最終的に StopIteration がスローされます。これを next(iterator, None) に変更して、ストリームの終了を適切に処理するようにします。
# ❌ 修正前 (Python 3.7+)
def my_generator(items):
    for i in items:
        if i == "stop":
            raise StopIteration # これにより RuntimeError が発生するようになります
        yield i

# ✅ 修正後
def my_generator(items):
    for i in items:
        if i == "stop":
            return # 終了するための適切な方法
        yield i

根本原因:なぜ壊れたのか?

Python 2.x や初期の 3.x の時代には、StopIteration を発生させることがジェネレータを終了させる標準的な方法でした。しかし、これには危険が伴いました。もしジェネレータ内のバグ(例えば、壊れたリスト内包表記など)によって誤って StopIteration が発生した場合、それを呼び出しているループは単に停止してしまいます。つまり、クラッシュが発生したという警告なしにデータが失われる可能性があったのです。

PEP 479 は、コードの堅牢性を高めるためにこの動作を変更しました。**Python 3.7(2018年リリース)**からデフォルトの動作として、ジェネレータから漏れ出した StopIteration は自動的に RuntimeError に変換されるようになりました。これにより、例外を黙って伝播させるのではなく、明示的に終了を処理することが強制されます。

この場合、次のようなトレースバックが表示されます。

RuntimeError: generator raised StopIteration

一般的なシナリオと解決策

1. 手動での StopIteration

古いスクリプトでは、ロジックを中断するために raise StopIteration がよく使われていました。これは現在、構文上の「アンチパターン」とされています。

修正方法: return を使用してください。ジェネレータにおいて return は単に関数を終了するだけでなく、イテレータに対してこれ以上 yield する値がないことを伝えます。これはクリーンで読みやすく、PEP 479 に準拠した方法です。

2. 「隠れた」原因:未処理の next() 呼び出し

これは、予期しないクラッシュの最も頻繁な原因です。内部のイテレータに対して next() を呼び出し、そのアイテムがなくなった場合、StopIteration が発生します。Python 3.7 以降では、これがジェネレータの外部に漏れるのをキャッチし、RuntimeError でプロセスを終了させます。

# ❌ 危険なコード
def process_pairs(data):
    it = iter(data)
    while True:
        # 'data' の要素数が奇数の場合、ここで next(it) がクラッシュします
        val1 = next(it) 
        val2 = next(it)
        yield val1 + val2

修正方法: 常に next() にデフォルト値を指定するか、try/except ブロックを使用してください。

# ✅ 安全なコード
def process_pairs(data):
    it = iter(data)
    while True:
        val1 = next(it, None)
        val2 = next(it, None)
        # データの終端に達したか確認
        if val1 is None or val2 is None:
            return 
        yield val1 + val2

3. 入れ子になったジェネレータ (yield from)

yield from sub_generator() を使用している場合、エラーは子関数の中に潜んでいることが多いです。サブジェネレータが StopIteration を発生させると、親ジェネレータもクラッシュします。

修正方法: サブジェネレータを監査してください。チェーン内のすべての関数が return を使用して終了していることを確認します。

検証:修正の確認

ジェネレータが安全であることを確認するには、list() コンストラクタを使用して最後まで使い切ってみてください。これにより、ジェネレータが終了ロジックに達するまで強制的に実行されます。

# 簡易検証スクリプト
try:
    gen = my_generator_function([1, 2, 3])
    results = list(gen)
    print(f"成功!{len(results)} 個のアイテムを処理しました。")
except RuntimeError as e:
    if "generator raised StopIteration" in str(e):
        print("エラー: PEP 479 の問題がまだ存在します。")
    else:
        print(f"別のエラーをキャッチしました: {e}")

list(gen)RuntimeError なしで完了すれば、そのコードはモダンな Python バージョンと完全に互換性があります。

参考文献

Related Error Notes