Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid "exception ignored" in python enhanced generator

I have a coroutine (Enhanced generators) in python with some code to be executed after the end of data:

def mycoroutine():
  try:
    while True:
      data = (yield)
      print data
  finally:
    raise ValueError
    print "END"

co = mycoroutine()
co.next()

for i in (1,2,3):
  co.send(i)

The ValueError exception is not raised but the interpreter simply prints:

Exception ValueError: ValueError() in <generator object mycoroutine at 0x2b59dfa23d20> ignored

Is there a way to catch the exception in the caller?

like image 218
Zac Avatar asked Sep 05 '13 12:09

Zac


1 Answers

The exception is raised. The finally block is executed when the generator is closed. Closing a generator is done by raising a GeneratorExit exception in the generator context.

The exception in ignored because the generator isn't closed until it is being deleted (automatically in this case, when Python exits); the generator __del__ handler closes the generator, which triggers the finally: block:

>>> def mycoroutine():
...   try:
...     while True:
...       data = (yield)
...       print data
...   finally:
...     raise ValueError
...     print "END"
... 
>>> co = mycoroutine()
>>> co.next()
>>> co.close()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in mycoroutine
ValueError
>>> co = mycoroutine()
>>> co.next()
>>> del co
Exception ValueError: ValueError() in <generator object mycoroutine at 0x1046a9fa0> ignored

Exceptions raised during cleanup are always ignored; see the object.__del__() documentation:

Warning: Due to the precarious circumstances under which __del__() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead.

The solution is to not have exceptions being raised when a generator is cleaned up, or catch the exception by closing the generator explicitly:

>>> co = mycoroutine()
>>> co.next()
>>> try:
...     co.close()
... except ValueError:
...     pass
... 
>>> del co
>>> # No exception was raised
... 

You could also catch the GeneratorExit exception and perform some cleanup at that point:

def mycoroutine():
  try:
    while True:
      data = (yield)
      print data
  except GeneratorExit:
    print "Generator exiting!"

but note that any exception other than StopIteration or GeneratorExit will always be propagated; see the generator.close() documentation:

If the generator function then raises StopIteration (by exiting normally, or due to already being closed) or GeneratorExit (by not catching the exception), close returns to its caller. If the generator yields a value, a RuntimeError is raised. If the generator raises any other exception, it is propagated to the caller.

like image 170
Martijn Pieters Avatar answered Oct 05 '22 14:10

Martijn Pieters