summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/exceptions.rst8
-rw-r--r--Doc/library/exceptions.rst23
-rw-r--r--Doc/library/stdtypes.rst9
-rw-r--r--Include/pyerrors.h4
-rw-r--r--Lib/test/test_exceptions.py15
-rw-r--r--Lib/test/test_raise.py7
-rw-r--r--Lib/test/test_sys.py6
-rw-r--r--Lib/traceback.py13
-rw-r--r--Misc/NEWS3
-rw-r--r--Objects/exceptions.c39
-rw-r--r--Python/ceval.c21
-rw-r--r--Python/pythonrun.c9
12 files changed, 79 insertions, 78 deletions
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index f181143..9a66b7f 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -471,10 +471,6 @@ Exception Objects
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 :const:`None`, the default
- exception display routines stop showing the context chain.
-
.. c:function:: void PyException_SetCause(PyObject *ex, PyObject *ctx)
@@ -482,9 +478,7 @@ Exception Objects
it. There is no type check to make sure that *ctx* is either an exception
instance or :const:`None`. This steals a reference to *ctx*.
- If the cause is set to :const:`None` the default exception display
- routines will not display this exception's context, and will not follow the
- chain any further.
+ :attr:`__suppress_context__` is implicitly set to ``True`` by this function.
.. _unicodeexceptions:
diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
index 33bc3b0..faf6732 100644
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -39,18 +39,17 @@ When raising (or re-raising) an exception in an :keyword:`except` clause
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 :const:`None`, and it will be 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 :const:`None`,
-while the default value for :attr:`__cause__` is :const:`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.
+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 ``None``. It will be set as :attr:`__cause__` on the raised exception.
+Setting :attr:`__cause__` implicitly sets the :attr:`__suppress_context__` to
+``True``. If :attr:`__cause__` is an exception, it will be displayed. If
+:attr:`__cause__` is present or :attr:`__suppress_context__` has a true value,
+:attr:`__context__` will not be displayed.
+
+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 --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index a34b1cf..bf86d8eb 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -2996,11 +2996,10 @@ It is written as ``None``.
The Ellipsis Object
-------------------
-This object is commonly used by slicing (see :ref:`slicings`), but may also
-be used in other situations where a sentinel value other than :const:`None`
-is needed. It supports no special operations. There is exactly one ellipsis
-object, named :const:`Ellipsis` (a built-in name). ``type(Ellipsis)()``
-produces the :const:`Ellipsis` singleton.
+This object is commonly used by slicing (see :ref:`slicings`). It supports no
+special operations. There is exactly one ellipsis object, named
+:const:`Ellipsis` (a built-in name). ``type(Ellipsis)()`` produces the
+:const:`Ellipsis` singleton.
It is written as ``Ellipsis`` or ``...``.
diff --git a/Include/pyerrors.h b/Include/pyerrors.h
index 1b80ecf..fb6281c 100644
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -10,7 +10,8 @@ extern "C" {
/* PyException_HEAD defines the initial segment of every exception class. */
#define PyException_HEAD PyObject_HEAD PyObject *dict;\
PyObject *args; PyObject *traceback;\
- PyObject *context; PyObject *cause;
+ PyObject *context; PyObject *cause;\
+ int suppress_context;
typedef struct {
PyException_HEAD
@@ -114,7 +115,6 @@ PyAPI_FUNC(PyObject *) PyException_GetTraceback(PyObject *);
/* Cause manipulation (PEP 3134) */
PyAPI_FUNC(PyObject *) PyException_GetCause(PyObject *);
PyAPI_FUNC(void) PyException_SetCause(PyObject *, PyObject *);
-PyAPI_FUNC(int) _PyException_SetCauseChecked(PyObject *, PyObject *);
/* Context manipulation (PEP 3134) */
PyAPI_FUNC(PyObject *) PyException_GetContext(PyObject *);
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 39ff85f..97762f9 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -388,18 +388,18 @@ class ExceptionTests(unittest.TestCase):
def testChainingAttrs(self):
e = Exception()
self.assertIsNone(e.__context__)
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertIsNone(e.__cause__)
e = TypeError()
self.assertIsNone(e.__context__)
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertIsNone(e.__cause__)
class MyException(EnvironmentError):
pass
e = MyException()
self.assertIsNone(e.__context__)
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertIsNone(e.__cause__)
def testChainingDescriptors(self):
try:
@@ -408,15 +408,16 @@ class ExceptionTests(unittest.TestCase):
e = exc
self.assertIsNone(e.__context__)
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertIsNone(e.__cause__)
+ self.assertFalse(e.__suppress_context__)
e.__context__ = NameError()
e.__cause__ = None
self.assertIsInstance(e.__context__, NameError)
self.assertIsNone(e.__cause__)
-
- e.__cause__ = Ellipsis
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertTrue(e.__suppress_context__)
+ e.__suppress_context__ = False
+ self.assertFalse(e.__suppress_context__)
def testKeywordArgs(self):
# test that builtin exception don't take keyword args,
diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py
index 8ae9210..b169867 100644
--- a/Lib/test/test_raise.py
+++ b/Lib/test/test_raise.py
@@ -174,11 +174,14 @@ class TestCause(unittest.TestCase):
raise ValueError from None
except ValueError as exc:
self.assertIsNone(exc.__cause__)
- raise exc from Ellipsis
+ self.assertTrue(exc.__suppress_context__)
+ exc.__suppress_context__ = False
+ raise exc
except ValueError as exc:
e = exc
- self.assertIs(e.__cause__, Ellipsis)
+ self.assertIsNone(e.__cause__)
+ self.assertFalse(e.__suppress_context__)
self.assertIsInstance(e.__context__, TypeError)
def test_invalid_cause(self):
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 2b9abfc..71dbd29 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -700,14 +700,14 @@ class SizeofTest(unittest.TestCase):
class C(object): pass
check(C.__dict__, size(h + 'P'))
# BaseException
- check(BaseException(), size(h + '5P'))
+ check(BaseException(), size(h + '5Pi'))
# UnicodeEncodeError
- check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5P 2P2PP'))
+ check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5Pi 2P2PP'))
# UnicodeDecodeError
# XXX
# check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP'))
# UnicodeTranslateError
- check(UnicodeTranslateError("", 0, 1, ""), size(h + '5P 2P2PP'))
+ check(UnicodeTranslateError("", 0, 1, ""), size(h + '5Pi 2P2PP'))
# ellipses
check(Ellipsis, size(h + ''))
# EncodingMap
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 35858af..eeb9e73 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -119,15 +119,16 @@ def _iter_chain(exc, custom_tb=None, seen=None):
seen = set()
seen.add(exc)
its = []
+ context = exc.__context__
cause = exc.__cause__
- if cause is Ellipsis:
- context = exc.__context__
- if context is not None and context not in seen:
- its.append(_iter_chain(context, None, seen))
- its.append([(_context_message, None)])
- elif cause is not None and cause not in seen:
+ if cause is not None and cause not in seen:
its.append(_iter_chain(cause, False, seen))
its.append([(_cause_message, None)])
+ elif (context is not None and
+ not exc.__suppress_context__ and
+ context not in seen):
+ its.append(_iter_chain(context, None, seen))
+ its.append([(_context_message, None)])
its.append([(exc, custom_tb or exc.__traceback__)])
# itertools.chain is in an extension module and may be unavailable
for it in its:
diff --git a/Misc/NEWS b/Misc/NEWS
index 3b8e3ca..37b319d 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 4?
Core and Builtins
-----------------
+- Issue #14133 (PEP 415): Implement suppression of __context__ display with an
+ attribute on BaseException. This replaces the original mechanism of PEP 409.
+
- Issue #14417: Mutating a dict during lookup now restarts the lookup instead
of raising a RuntimeError (undoes issue #14205).
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index a11283e..b994862 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -42,6 +42,7 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* the dict is created on the fly in PyObject_GenericSetAttr */
self->dict = NULL;
self->traceback = self->cause = self->context = NULL;
+ self->suppress_context = 0;
self->args = PyTuple_New(0);
if (!self->args) {
@@ -266,35 +267,28 @@ BaseException_get_cause(PyObject *self) {
PyObject *res = PyException_GetCause(self);
if (res)
return res; /* new reference already returned above */
- Py_INCREF(Py_Ellipsis);
- return Py_Ellipsis;
+ Py_RETURN_NONE;
}
-int
-_PyException_SetCauseChecked(PyObject *self, PyObject *arg) {
- if (arg == Py_Ellipsis) {
+static int
+BaseException_set_cause(PyObject *self, PyObject *arg) {
+ if (arg == NULL) {
+ PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
+ return -1;
+ } else if (arg == Py_None) {
arg = NULL;
- } else if (arg != Py_None && !PyExceptionInstance_Check(arg)) {
- PyErr_SetString(PyExc_TypeError, "exception cause must be None, "
- "Ellipsis or derive from BaseException");
+ } else if (!PyExceptionInstance_Check(arg)) {
+ PyErr_SetString(PyExc_TypeError, "exception cause must be None "
+ "or derive from BaseException");
return -1;
} else {
- /* PyException_SetCause steals a reference */
+ /* PyException_SetCause steals this reference */
Py_INCREF(arg);
}
PyException_SetCause(self, arg);
return 0;
}
-static int
-BaseException_set_cause(PyObject *self, PyObject *arg) {
- if (arg == NULL) {
- PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
- return -1;
- }
- return _PyException_SetCauseChecked(self, arg);
-}
-
static PyGetSetDef BaseException_getset[] = {
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
@@ -333,6 +327,7 @@ void
PyException_SetCause(PyObject *self, PyObject *cause) {
PyObject *old_cause = ((PyBaseExceptionObject *)self)->cause;
((PyBaseExceptionObject *)self)->cause = cause;
+ ((PyBaseExceptionObject *)self)->suppress_context = 1;
Py_XDECREF(old_cause);
}
@@ -352,6 +347,12 @@ PyException_SetContext(PyObject *self, PyObject *context) {
}
+static struct PyMemberDef BaseException_members[] = {
+ {"__suppress_context__", T_BOOL,
+ offsetof(PyBaseExceptionObject, suppress_context)}
+};
+
+
static PyTypeObject _PyExc_BaseException = {
PyVarObject_HEAD_INIT(NULL, 0)
"BaseException", /*tp_name*/
@@ -382,7 +383,7 @@ static PyTypeObject _PyExc_BaseException = {
0, /* tp_iter */
0, /* tp_iternext */
BaseException_methods, /* tp_methods */
- 0, /* tp_members */
+ BaseException_members, /* tp_members */
BaseException_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
diff --git a/Python/ceval.c b/Python/ceval.c
index b9a006b..718bb32 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3572,23 +3572,26 @@ do_raise(PyObject *exc, PyObject *cause)
if (cause) {
PyObject *fixed_cause;
- int result;
if (PyExceptionClass_Check(cause)) {
fixed_cause = PyObject_CallObject(cause, NULL);
if (fixed_cause == NULL)
goto raise_error;
- Py_CLEAR(cause);
- } else {
- /* Let "exc.__cause__ = cause" handle all further checks */
+ Py_DECREF(cause);
+ }
+ else if (PyExceptionInstance_Check(cause)) {
fixed_cause = cause;
- cause = NULL; /* Steal the reference */
}
- /* We retain ownership of the reference to fixed_cause */
- result = _PyException_SetCauseChecked(value, fixed_cause);
- Py_DECREF(fixed_cause);
- if (result < 0) {
+ else if (cause == Py_None) {
+ Py_DECREF(cause);
+ fixed_cause = NULL;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "exception causes must derive from "
+ "BaseException");
goto raise_error;
}
+ PyException_SetCause(value, fixed_cause);
}
PyErr_SetObject(type, value);
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index cd3cf5c..d9eb5e7 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -1761,11 +1761,7 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
else if (PyExceptionInstance_Check(value)) {
cause = PyException_GetCause(value);
context = PyException_GetContext(value);
- if (cause && cause == Py_None) {
- /* print neither cause nor context */
- ;
- }
- else if (cause) {
+ if (cause) {
res = PySet_Contains(seen, cause);
if (res == -1)
PyErr_Clear();
@@ -1776,7 +1772,8 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
cause_message, f);
}
}
- else if (context) {
+ else if (context &&
+ !((PyBaseExceptionObject *)value)->suppress_context) {
res = PySet_Contains(seen, context);
if (res == -1)
PyErr_Clear();