Author vstinner
Recipients vstinner
Date 2015-01-31.00:48:19
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1422665300.82.0.889579755378.issue23353@psf.upfronthosting.co.za>
In-reply-to
Content
Attached gen_exc_value.patch changes how generators handle the currently handled exception (tstate->exc_value). The patch probably lacks tests to test the exact behaviour of sys.exc_info(). The 3 examples below can be used to write such tests. But before investing time on an implemenation, I would like to ensure that I correctly understood the bug and discuss how it should be fixed.

Currently, a generator inherits the currently handled exception from the "caller" (function calling next(), gen.send() or gen.throw()). With the patch, the generator and its caller don't share the exception anymore. The generator still remembers the current exception when it is interrupted by yield.

It's still possible to pass the exception from the caller to the generator using the throw() method of the generator. My patch doesn't change the behaviour of throw(): see the example 3 below.

The patch also fixes the initial issue: "./python -m test test_asyncio test_functools" doesn't fail anymore.

Python 2.7 is also affected by the bug. I don't know yet if it's safe to change the behaviour of currently handled exception in Python 2 generators. It may break backward compatibility. We should probably check applications which heavily depends on generators. For example, the Trollius and Twisted projects use generators for coroutines in asynchronous programming.

The backward compatibility also matters in Python 3.4, so same question: should we change the behaviour of Python 3.4? Can it break applications?

In Python 3, the currently handled exception is more important than in Python 2, because it is used to automatically fill the __context__ attribute of raised exceptions.

I didn't test the behaviour of yield-from yet, I don't know how my patch changes its behaviour.


Example 1:
---
import sys

def gen():
    print(sys.exc_info())
    yield

g = gen()
try:
    raise ValueError
except Exception:
    next(g)
---

Original output:
---
(<class 'ValueError'>, ValueError(), <traceback object at 0x7f22a1ab52c8>)
---

With the patch:
---
(None, None, None)
---


Example 2:
---
import sys

def gen():
    try:
        yield
        raise TypeError()
    except:
        print(sys.exc_info())
    print(sys.exc_info())
    yield

g = gen()
next(g)
try:
    raise ValueError
except Exception:
    next(g)
---

Original output:
---
(<class 'TypeError'>, TypeError(), <traceback object at 0x7fad239a22c8>)
(<class 'ValueError'>, ValueError(), <traceback object at 0x7fad239a2288>)
---

With the patch:
---
(<class 'TypeError'>, TypeError(), <traceback object at 0x7f278b174988>)
(None, None, None)
---


Example 3:
---
import sys

def gen():
    try:
        try:
            yield
        except:
            print(sys.exc_info())
            raise TypeError()
    except Exception as exc:
        print("TypeError context:", repr(exc.__context__))
    yield

g = gen()
next(g)
try:
    raise ValueError
except Exception as exc:
    g.throw(exc)
---

Original output:
---
(<class 'ValueError'>, ValueError(), <traceback object at 0x7f233f05e388>)
TypeError context: ValueError()
(<class 'ValueError'>, ValueError(), <traceback object at 0x7f233f05e348>)
---

With the patch (unchanged):
---
(<class 'ValueError'>, ValueError(), <traceback object at 0x7fdf356fead8>)
TypeError context: ValueError()
(None, None, None)
---
History
Date User Action Args
2015-01-31 00:48:21vstinnersetrecipients: + vstinner
2015-01-31 00:48:20vstinnersetmessageid: <1422665300.82.0.889579755378.issue23353@psf.upfronthosting.co.za>
2015-01-31 00:48:20vstinnerlinkissue23353 messages
2015-01-31 00:48:20vstinnercreate