views:

50

answers:

2

I recently came across some surprising behaviour in Python generators:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except:
      print '*Excepted Successfully*'
      # raise

for i in YieldOne():
  raise Exception('test exception')

Which gives the output:

*Excepted Successfully*
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: test exception

I was (pleasantly) surprised that *Excepted Successfully* got printed, as this was what I wanted, but also surprised that the Exception still got propagated up to the top level. I was expecting to have to use the (commented in this example) raise keyword to get the observed behaviour.

Can anyone explain why this functionality works as it does, and why the except in the generator doesn't swallow the exception?

Is this the only instance in Python where an except doesn't swallow an exception?

+5  A: 

Your code does not do what you think it does. You cannot raise Exceptions in a coroutine like this. What you do instead is catching the GeneratorExit exception. See what happens when you use a different Exception:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except RuntimeError:
        print "you won't see this"
    except GeneratorExit:
      print 'this is what you saw before'
      # raise

for i in YieldOne():
  raise RuntimeError
THC4k
Aha, now it makes sense. I didn't originally expect the exception to propogate 'over' to the generator.
EoghanM
+1 very interesting!
rubik
+1  A: 

EDIT: What THC4k said.

If you really want to raise an arbitrary exception inside a generator, use the throw method:

>>> def Gen():
...     try:
...             yield 1
...     except Exception:
...             print "Excepted."
...
>>> foo = Gen()
>>> next(foo)
1
>>> foo.throw(Exception())
Excepted.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

You'll notice that you get a StopIteration at the top level. These are raised by generators which have run out of elements; they are usually swallowed by the for loop but in this case we made the generator raise an exception so the loop doesn't notice them.

katrielalex