Every call to next(x) returns the new value from the x iterator unless an exception is raised. If this is StopIteration, it means the iterator is exhausted and can supply no more values. If a generator is iterated, it automatically raises StopIteration upon the end of the body:
>>> def one_two():
... yield 1
... yield 2
...
>>> i = one_two()
>>> next(i)
1
>>> next(i)
2
>>> next(i)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIterationStopIteration is automatically handled by tools that calls next for you:
>>> list(one_two())
[1, 2]
The problem is, any unexpected StopIteration that is raised within a generator causes it to stop silently instead of actually raising an exception:
def one_two():
yield 1
yield 2
def one_two_repeat(n):
for _ in range(n):
i = one_two()
yield next(i)
yield next(i)
yield next(i)
print(list(one_two_repeat(3)))
The last yield here is a mistake: StopIteration is raised and makes list(...) to stop the iteration. The result is [1, 2], surprisingly.
However, that was changed in Python 3.7. Such foreign StopIteration is now replaced with RuntimeError:
Traceback (most recent call last):
File "test.py", line 10, in one_two_repeat
yield next(i)
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test.py", line 12, in <module>
print(list(one_two_repeat(3)))
RuntimeError: generator raised StopIteration
You can enable the same behavior since python3.5 by from __future__ import generator_stop.