diff -r 52f68c95e025 Doc/ACKS.txt --- a/Doc/ACKS.txt Thu Jan 26 08:47:27 2012 -0600 +++ b/Doc/ACKS.txt Thu Feb 02 13:58:45 2012 -0800 @@ -62,6 +62,7 @@ * Stefan Franke * Jim Fulton * Peter Funk + * Ethan Furman * Lele Gaifax * Matthew Gallagher * Gabriel Genellina diff -r 52f68c95e025 Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Thu Jan 26 08:47:27 2012 -0600 +++ b/Doc/c-api/exceptions.rst Thu Feb 02 13:58:45 2012 -0800 @@ -421,17 +421,24 @@ .. 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 an 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 Ellipsis``). 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 52f68c95e025 Doc/library/exceptions.rst --- a/Doc/library/exceptions.rst Thu Jan 26 08:47:27 2012 -0600 +++ b/Doc/library/exceptions.rst Thu Feb 02 13:58:45 2012 -0800 @@ -34,6 +34,24 @@ 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. (Note: the default value for :attr:`__context__` is :keyword:`None`, +while the default value for :attr:`__cause__` is :class:`Ellipsis`.) + +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 52f68c95e025 Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Thu Jan 26 08:47:27 2012 -0600 +++ b/Lib/test/test_exceptions.py Thu Feb 02 13:58:45 2012 -0800 @@ -387,19 +387,35 @@ def testChainingAttrs(self): e = Exception() - self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertIsNone(e.__context__) + self.assertIs(e.__cause__, Ellipsis) e = TypeError() - self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertIsNone(e.__context__) + self.assertIs(e.__cause__, Ellipsis) class MyException(EnvironmentError): pass e = MyException() - self.assertEqual(e.__context__, None) - self.assertEqual(e.__cause__, None) + self.assertIsNone(e.__context__) + self.assertIs(e.__cause__, Ellipsis) + + try: + raise Exception() + except Exception as exc: + e = exc + + self.assertIsNone(e.__context__) + self.assertIs(e.__cause__, Ellipsis) + + e.__context__ = NameError() + e.__cause__ = None + self.assertIsInstance(e.__context__, NameError) + self.assertIsNone(e.__cause__) + + e.__cause__ = Ellipsis + self.assertIs(e.__cause__, Ellipsis) def testKeywordArgs(self): # test that builtin exception don't take keyword args, diff -r 52f68c95e025 Lib/test/test_raise.py --- a/Lib/test/test_raise.py Thu Jan 26 08:47:27 2012 -0600 +++ b/Lib/test/test_raise.py Thu Feb 02 13:58:45 2012 -0800 @@ -77,6 +77,16 @@ 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 52f68c95e025 Lib/traceback.py --- a/Lib/traceback.py Thu Jan 26 08:47:27 2012 -0600 +++ b/Lib/traceback.py Thu Feb 02 13:58:45 2012 -0800 @@ -120,8 +120,8 @@ 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 not in (Ellipsis, None) and cause not in seen: + its.append(_iter_chain(cause, False, seen)) its.append([(_cause_message, None)]) else: context = exc.__context__ diff -r 52f68c95e025 Objects/exceptions.c --- a/Objects/exceptions.c Thu Jan 26 08:47:27 2012 -0600 +++ b/Objects/exceptions.c Thu Feb 02 13:58:45 2012 -0800 @@ -293,7 +293,8 @@ BaseException_get_cause(PyObject *self) { PyObject *res = PyException_GetCause(self); if (res) return res; /* new reference already returned above */ - Py_RETURN_NONE; + Py_INCREF(Py_Ellipsis); + return Py_Ellipsis; } static int @@ -301,8 +302,11 @@ if (arg == NULL) { PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted"); return -1; + } else if (arg == Py_Ellipsis) { + arg = NULL; } else if (arg == Py_None) { - arg = NULL; + /* let __cause__ be set to None */ + ; } else if (!PyExceptionInstance_Check(arg)) { PyErr_SetString(PyExc_TypeError, "exception cause must be None " "or derive from BaseException"); diff -r 52f68c95e025 Python/ceval.c --- a/Python/ceval.c Thu Jan 26 08:47:27 2012 -0600 +++ b/Python/ceval.c Thu Feb 02 13:58:45 2012 -0800 @@ -3576,6 +3576,11 @@ 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 52f68c95e025 Python/pythonrun.c --- a/Python/pythonrun.c Thu Jan 26 08:47:27 2012 -0600 +++ b/Python/pythonrun.c Thu Feb 02 13:58:45 2012 -0800 @@ -1691,7 +1691,11 @@ 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();