TL;DR: クイック修正
モダンな Python バージョンに移行した後にコードが壊れた場合、通常は構文を少し変更するだけで修正できます。主に2つの選択肢があります。
raise StopIterationをreturnに置き換える: 現代の 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 バージョンと完全に互換性があります。

