4

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
  • `print` each of those arguments to `yield`; you'll see the difference. – Prune Sep 16 '20 at 20:54
  • I could not repeat the problem, it works for me: >>> tuple(next(it) for it in iters); (1, 3, 5) – antont Sep 16 '20 at 20:55
  • @Prune This is not a duplicate. This is a valid bug I believe, where the `StopIteration` exception should be caught and handled by the generator expression instead of being re-raised as a `RuntimeError`. – blhsing Sep 16 '20 at 20:57
  • @antont See demo: https://repl.it/@blhsing/VagueFreshKernel – blhsing Sep 16 '20 at 20:58
  • @blhsing I see it now; I fiddled more with the code, and .... yes, you're right. – Prune Sep 16 '20 at 21:02
  • Ok so the tuple for the generator expression works, but then it just raises the StopIteration when the generator is exchausted.It's interesting and well seems strange that the exception is not caught though. – antont Sep 16 '20 at 21:09

1 Answers1

5

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.

blhsing
  • 91,368
  • 6
  • 71
  • 106