TL;DR: The Quick Fix
If your code broke after migrating to a modern Python version, you can usually fix it with a quick syntax change. You have two primary options:
- Replace
raise StopIterationwithreturn: In modern Python,returnis the correct way to signal a generator is finished. - Protect
next()calls: If you usenext(iterator)inside a generator, it will eventually throwStopIteration. Change this tonext(iterator, None)to handle the end of the stream gracefully.
# ❌ BROKEN (Python 3.7+)
def my_generator(items):
for i in items:
if i == "stop":
raise StopIteration # This now triggers a RuntimeError
yield i
# ✅ FIXED
def my_generator(items):
for i in items:
if i == "stop":
return # The proper way to exit
yield i
The Root Cause: Why did it break?
Back in the Python 2.x and early 3.x days, raising StopIteration was the standard way to exit a generator. It worked, but it was dangerous. If a bug inside your generator triggered a StopIteration by mistake—perhaps from a broken list comprehension—the loop calling it would simply stop. You would lose data without any warning that a crash had occurred.
PEP 479 changed this behavior to make code more robust. Starting as the default in Python 3.7 (released in 2018), any StopIteration that leaks out of a generator is automatically converted into a RuntimeError. This forces you to handle the exit explicitly rather than letting exceptions bubble up silently.
You will see this specific traceback:
RuntimeError: generator raised StopIteration
Common Scenarios and Solutions
1. Manual StopIteration
Legacy scripts often use raise StopIteration to break logic. This is now a syntax "anti-pattern."
The Fix: Use return. In a generator, return doesn't just exit the function; it tells the iterator that there are no more values to yield. It is clean, readable, and PEP 479 compliant.
2. The 'Hidden' Killer: Unhandled next() calls
This is the most frequent cause of unexpected crashes. If you call next() on an internal iterator and it runs out of items, it raises StopIteration. Python 3.7+ catches this leaving your generator and kills the process with a RuntimeError.
# ❌ DANGEROUS CODE
def process_pairs(data):
it = iter(data)
while True:
# If 'data' has an odd number of items, next(it) crashes here
val1 = next(it)
val2 = next(it)
yield val1 + val2
The Fix: Always provide a default value to next() or use a try/except block.
# ✅ SAFE CODE
def process_pairs(data):
it = iter(data)
while True:
val1 = next(it, None)
val2 = next(it, None)
# Check if we hit the end of the data
if val1 is None or val2 is None:
return
yield val1 + val2
3. Nested Generators (yield from)
When using yield from sub_generator(), the error often lives inside the child function. If the sub-generator raises StopIteration, the parent will crash too.
The Fix: Audit your sub-generators. Ensure every function in the chain uses return to terminate.
Verification: Confirming the Fix
To ensure your generator is safe, try exhausting it completely using the list() constructor. This forces the generator to run until it hits its exit logic.
# Simple validation script
try:
gen = my_generator_function([1, 2, 3])
results = list(gen)
print(f"Success! Processed {len(results)} items.")
except RuntimeError as e:
if "generator raised StopIteration" in str(e):
print("Error: The PEP 479 issue still exists.")
else:
print(f"Caught a different error: {e}")
If list(gen) finishes without a RuntimeError, your code is fully compatible with modern Python versions.

