diff -r aa214fefbdc1 Doc/ACKS.txt --- a/Doc/ACKS.txt Sat Jan 28 20:33:52 2012 -0500 +++ b/Doc/ACKS.txt Tue Jan 31 23:14:15 2012 -0800 @@ -62,6 +62,7 @@ docs@python.org), and we'll be glad to c * Stefan Franke * Jim Fulton * Peter Funk + * Ethan Furman * Lele Gaifax * Matthew Gallagher * Gabriel Genellina diff -r aa214fefbdc1 Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Sat Jan 28 20:33:52 2012 -0500 +++ b/Doc/c-api/exceptions.rst Tue Jan 31 23:14:15 2012 -0800 @@ -421,17 +421,24 @@ Exception Objects .. c:function:: PyObject* PyException_GetCause(PyObject *ex) - Return the cause (another exception instance set by ``raise ... from ...``) - associated with the exception as a new reference, as accessible from Python - through :attr:`__cause__`. If there is no cause associated, this returns - *NULL*. + Return the cause (either another exception instance, or :keyword:`None`, + set by ``raise ... from ...``) associated with the exception as a new + reference, as accessible from Python through :attr:`__cause__`. + + If there is no cause associated, this returns *NULL* (from Python + ``__cause__ is False``). If the cause is :keyword:`None`, the default + exception display routines stop showing the context chain. .. c:function:: void PyException_SetCause(PyObject *ex, PyObject *ctx) Set the cause associated with the exception to *ctx*. Use *NULL* to clear - it. There is no type check to make sure that *ctx* is an exception instance. - This steals a reference to *ctx*. + it. There is no type check to make sure that *ctx* is either an exception + instance or :keyword:`None`. This steals a reference to *ctx*. + + If the cause is set to :keyword:`None` the default exception display + routines will not display this exception's context, and will not follow the + chain any further. .. _unicodeexceptions: diff -r aa214fefbdc1 Doc/library/exceptions.rst --- a/Doc/library/exceptions.rst Sat Jan 28 20:33:52 2012 -0500 +++ b/Doc/library/exceptions.rst Tue Jan 31 23:14:15 2012 -0800 @@ -34,6 +34,23 @@ programmers are encouraged to at least d defining exceptions is available in the Python Tutorial under :ref:`tut-userexceptions`. +When raising (or re-raising) an exception in an :keyword:`except` clause +:attr:`__context__` is automatically set to the last exception caught; if the +new exception is not handled the traceback that is eventually displayed will +include the originating exception(s) and the final exception. + +This implicit exception chain can be made explicit by using :keyword:`from` +with :keyword:`raise`. The single argument to :keyword:`from` must be an +exception or :keyword:`None`, and it will bet set as :attr:`__cause__` on the +raised exception. If :attr:`__cause__` is an exception it will be displayed +instead of :attr:`__context__`; if :attr:`__cause__` is None, +:attr:`__context__` will not be displayed by the default exception handling +code. + +In either case, the default exception handling code will not display +any of the remaining links in the :attr:`__context__` chain if +:attr:`__cause__` has been set. + Base classes ------------ diff -r aa214fefbdc1 Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Sat Jan 28 20:33:52 2012 -0500 +++ b/Lib/test/test_exceptions.py Tue Jan 31 23:14:15 2012 -0800 @@ -388,18 +388,18 @@ class ExceptionTests(unittest.TestCase): def testChainingAttrs(self): e = Exception() self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertEqual(e.__cause__, False) e = TypeError() self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertEqual(e.__cause__, False) class MyException(EnvironmentError): pass e = MyException() self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertEqual(e.__cause__, False) def testKeywordArgs(self): # test that builtin exception don't take keyword args, diff -r aa214fefbdc1 Lib/test/test_raise.py --- a/Lib/test/test_raise.py Sat Jan 28 20:33:52 2012 -0500 +++ b/Lib/test/test_raise.py Tue Jan 31 23:14:15 2012 -0800 @@ -77,6 +77,16 @@ class TestRaise(unittest.TestCase): nested_reraise() self.assertRaises(TypeError, reraise) + def test_raise_from_None(self): + try: + try: + raise TypeError("foo") + except: + raise ValueError() from None + except ValueError as e: + self.assertTrue(isinstance(e.__context__, TypeError)) + self.assertIsNone(e.__cause__) + def test_with_reraise1(self): def reraise(): try: diff -r aa214fefbdc1 Lib/traceback.py --- a/Lib/traceback.py Sat Jan 28 20:33:52 2012 -0500 +++ b/Lib/traceback.py Tue Jan 31 23:14:15 2012 -0800 @@ -120,8 +120,8 @@ def _iter_chain(exc, custom_tb=None, see seen.add(exc) its = [] cause = exc.__cause__ - if cause is not None and cause not in seen: - its.append(_iter_chain(cause, None, seen)) + if cause is not False and cause is not None and cause not in seen: + its.append(_iter_chain(cause, False, seen)) its.append([(_cause_message, None)]) else: context = exc.__context__ diff -r aa214fefbdc1 Objects/exceptions.c --- a/Objects/exceptions.c Sat Jan 28 20:33:52 2012 -0500 +++ b/Objects/exceptions.c Tue Jan 31 23:14:15 2012 -0800 @@ -293,7 +293,7 @@ static PyObject * BaseException_get_cause(PyObject *self) { PyObject *res = PyException_GetCause(self); if (res) return res; /* new reference already returned above */ - Py_RETURN_NONE; + Py_RETURN_FALSE; } static int diff -r aa214fefbdc1 Python/ceval.c --- a/Python/ceval.c Sat Jan 28 20:33:52 2012 -0500 +++ b/Python/ceval.c Tue Jan 31 23:14:15 2012 -0800 @@ -3576,6 +3576,11 @@ do_raise(PyObject *exc, PyObject *cause) else if (PyExceptionInstance_Check(cause)) { fixed_cause = cause; } + else if (cause == Py_None) { + fixed_cause = Py_None; + Py_INCREF(Py_None); + Py_DECREF(cause); + } else { PyErr_SetString(PyExc_TypeError, "exception causes must derive from " diff -r aa214fefbdc1 Python/pythonrun.c --- a/Python/pythonrun.c Sat Jan 28 20:33:52 2012 -0500 +++ b/Python/pythonrun.c Tue Jan 31 23:14:15 2012 -0800 @@ -1691,7 +1691,11 @@ print_exception_recursive(PyObject *f, P else if (PyExceptionInstance_Check(value)) { cause = PyException_GetCause(value); context = PyException_GetContext(value); - if (cause) { + if (cause && cause == Py_None) { + /* print neither cause nor context */ + ; + } + else if (cause) { res = PySet_Contains(seen, cause); if (res == -1) PyErr_Clear();