9

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?

Zac
  • 2,180
  • 2
  • 23
  • 36

1 Answers1

14

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • The same thing happens if I replace `finally`" with `except GeneratorExit:`. I suppose it is the same... – Zac Sep 05 '13 at 13:21
  • @Zac: of course it does, you still *raise* a `ValueError`, don't you? Catching `GeneratorExit` and raising another exception does not prevent that exception from propagating. – Martijn Pieters Sep 05 '13 at 13:22
  • Thanks so much for that link to the documentation. You saved me from hours of debuggin. Pretty bizarre that they don't capture a stack trace in the destructor. – Walter Nissen Jul 24 '20 at 21:00
  • @WalterNissen: why is that bizarre? When the interpreter is exiting, there is no stack any more to trace. – Martijn Pieters Jul 25 '20 at 15:45
  • Because it's impossible to find where the ignored exception is being generated. It doesn't list the line or even the class, just the exception. We have 75K lines of Python and tracking down the one `__del__` that was doing an import and therefore causing an `ImportError` was not easy. – Walter Nissen Jul 26 '20 at 17:08
  • @WalterNissen right, but there is no traceback because there is no stack. There isn’t anything else to say there. Make sure your classes have good `__repr__` methods to identify them in the *Exception ... in [object_repr] ignored* error that’s written to stderr. – Martijn Pieters Jul 26 '20 at 17:40
  • All I will say is that I am glad, e.g., syntax errors indicate the line in which they occur, rather than leaving me to guess. – Walter Nissen Jul 28 '20 at 16:02