Why does this code work well and does not throw exceptions?
def myzip(*args):
iters = [iter(arg) for arg in args]
try:
while True:
yield tuple([next(it) for it in iters])
except StopIteration:
return
for x, y, z in myzip([1, 2], [3, 4], [5, 6]):
print(x, y, z)
But if this line
yield tuple([next(it) for it in iters])
replace by
yield tuple(next(it) for it in iters)
then everything stops working and throws a RuntimeError
?
This is a feature introduced in Python 3.5, rather than a bug. Per PEP-479, a RuntimeError
is re-raised intentionally when a StopIteration
is raised from inside a generator so that iterations based on the generator can now only be stopped if the generator returns, at which point a StopIteration
exception is raised to stop the iterations.
Otherwise, prior to Python 3.5, a StopIteration
exception raised anywhere in a generator will stop the generator rather than getting propagated, so that in case of:
a = list(F(x) for x in xs)
a = [F(x) for x in xs]
The former would get a truncated result if F(x)
raises a StopIteration
exception at some point during the iteration, which makes it hard to debug, while the latter would propagate the exception raised from F(x)
. The goal of the feature is to make the two statements behave the same, which is why the change affects generators but not list comprehensions.