PEP 415: Implement suppression of __context__ display with an exception attribute
This replaces the original PEP 409 implementation. See #14133.
This commit is contained in:
parent
d91dc62379
commit
d5a1c44455
|
@ -471,10 +471,6 @@ Exception Objects
|
||||||
set by ``raise ... from ...``) associated with the exception as a new
|
set by ``raise ... from ...``) associated with the exception as a new
|
||||||
reference, as accessible from Python through :attr:`__cause__`.
|
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)
|
.. 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
|
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*.
|
instance or :const:`None`. This steals a reference to *ctx*.
|
||||||
|
|
||||||
If the cause is set to :const:`None` the default exception display
|
:attr:`__suppress_context__` is implicitly set to ``True`` by this function.
|
||||||
routines will not display this exception's context, and will not follow the
|
|
||||||
chain any further.
|
|
||||||
|
|
||||||
|
|
||||||
.. _unicodeexceptions:
|
.. _unicodeexceptions:
|
||||||
|
|
|
@ -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
|
new exception is not handled the traceback that is eventually displayed will
|
||||||
include the originating exception(s) and the final exception.
|
include the originating exception(s) and the final exception.
|
||||||
|
|
||||||
This implicit exception chain can be made explicit by using :keyword:`from`
|
This implicit exception chain can be made explicit by using :keyword:`from` with
|
||||||
with :keyword:`raise`. The single argument to :keyword:`from` must be an
|
:keyword:`raise`. The single argument to :keyword:`from` must be an exception
|
||||||
exception or :const:`None`, and it will be set as :attr:`__cause__` on the
|
or ``None``. It will be set as :attr:`__cause__` on the raised exception.
|
||||||
raised exception. If :attr:`__cause__` is an exception it will be displayed
|
Setting :attr:`__cause__` implicitly sets the :attr:`__suppress_context__` to
|
||||||
instead of :attr:`__context__`; if :attr:`__cause__` is None,
|
``True``. If :attr:`__cause__` is an exception, it will be displayed. If
|
||||||
:attr:`__context__` will not be displayed by the default exception handling
|
:attr:`__cause__` is present or :attr:`__suppress_context__` has a true value,
|
||||||
code. (Note: the default value for :attr:`__context__` is :const:`None`,
|
:attr:`__context__` will not be displayed.
|
||||||
while the default value for :attr:`__cause__` is :const:`Ellipsis`.)
|
|
||||||
|
|
||||||
In either case, the default exception handling code will not display
|
In either case, the default exception handling code will not display any of the
|
||||||
any of the remaining links in the :attr:`__context__` chain if
|
remaining links in the :attr:`__context__` chain if :attr:`__cause__` has been
|
||||||
:attr:`__cause__` has been set.
|
set.
|
||||||
|
|
||||||
|
|
||||||
Base classes
|
Base classes
|
||||||
|
|
|
@ -2996,11 +2996,10 @@ It is written as ``None``.
|
||||||
The Ellipsis Object
|
The Ellipsis Object
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
This object is commonly used by slicing (see :ref:`slicings`), but may also
|
This object is commonly used by slicing (see :ref:`slicings`). It supports no
|
||||||
be used in other situations where a sentinel value other than :const:`None`
|
special operations. There is exactly one ellipsis object, named
|
||||||
is needed. It supports no special operations. There is exactly one ellipsis
|
:const:`Ellipsis` (a built-in name). ``type(Ellipsis)()`` produces the
|
||||||
object, named :const:`Ellipsis` (a built-in name). ``type(Ellipsis)()``
|
:const:`Ellipsis` singleton.
|
||||||
produces the :const:`Ellipsis` singleton.
|
|
||||||
|
|
||||||
It is written as ``Ellipsis`` or ``...``.
|
It is written as ``Ellipsis`` or ``...``.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ extern "C" {
|
||||||
/* PyException_HEAD defines the initial segment of every exception class. */
|
/* PyException_HEAD defines the initial segment of every exception class. */
|
||||||
#define PyException_HEAD PyObject_HEAD PyObject *dict;\
|
#define PyException_HEAD PyObject_HEAD PyObject *dict;\
|
||||||
PyObject *args; PyObject *traceback;\
|
PyObject *args; PyObject *traceback;\
|
||||||
PyObject *context; PyObject *cause;
|
PyObject *context; PyObject *cause;\
|
||||||
|
int suppress_context;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyException_HEAD
|
PyException_HEAD
|
||||||
|
@ -114,7 +115,6 @@ PyAPI_FUNC(PyObject *) PyException_GetTraceback(PyObject *);
|
||||||
/* Cause manipulation (PEP 3134) */
|
/* Cause manipulation (PEP 3134) */
|
||||||
PyAPI_FUNC(PyObject *) PyException_GetCause(PyObject *);
|
PyAPI_FUNC(PyObject *) PyException_GetCause(PyObject *);
|
||||||
PyAPI_FUNC(void) PyException_SetCause(PyObject *, PyObject *);
|
PyAPI_FUNC(void) PyException_SetCause(PyObject *, PyObject *);
|
||||||
PyAPI_FUNC(int) _PyException_SetCauseChecked(PyObject *, PyObject *);
|
|
||||||
|
|
||||||
/* Context manipulation (PEP 3134) */
|
/* Context manipulation (PEP 3134) */
|
||||||
PyAPI_FUNC(PyObject *) PyException_GetContext(PyObject *);
|
PyAPI_FUNC(PyObject *) PyException_GetContext(PyObject *);
|
||||||
|
|
|
@ -388,18 +388,18 @@ class ExceptionTests(unittest.TestCase):
|
||||||
def testChainingAttrs(self):
|
def testChainingAttrs(self):
|
||||||
e = Exception()
|
e = Exception()
|
||||||
self.assertIsNone(e.__context__)
|
self.assertIsNone(e.__context__)
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertIsNone(e.__cause__)
|
||||||
|
|
||||||
e = TypeError()
|
e = TypeError()
|
||||||
self.assertIsNone(e.__context__)
|
self.assertIsNone(e.__context__)
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertIsNone(e.__cause__)
|
||||||
|
|
||||||
class MyException(EnvironmentError):
|
class MyException(EnvironmentError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
e = MyException()
|
e = MyException()
|
||||||
self.assertIsNone(e.__context__)
|
self.assertIsNone(e.__context__)
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertIsNone(e.__cause__)
|
||||||
|
|
||||||
def testChainingDescriptors(self):
|
def testChainingDescriptors(self):
|
||||||
try:
|
try:
|
||||||
|
@ -408,15 +408,16 @@ class ExceptionTests(unittest.TestCase):
|
||||||
e = exc
|
e = exc
|
||||||
|
|
||||||
self.assertIsNone(e.__context__)
|
self.assertIsNone(e.__context__)
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertIsNone(e.__cause__)
|
||||||
|
self.assertFalse(e.__suppress_context__)
|
||||||
|
|
||||||
e.__context__ = NameError()
|
e.__context__ = NameError()
|
||||||
e.__cause__ = None
|
e.__cause__ = None
|
||||||
self.assertIsInstance(e.__context__, NameError)
|
self.assertIsInstance(e.__context__, NameError)
|
||||||
self.assertIsNone(e.__cause__)
|
self.assertIsNone(e.__cause__)
|
||||||
|
self.assertTrue(e.__suppress_context__)
|
||||||
e.__cause__ = Ellipsis
|
e.__suppress_context__ = False
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertFalse(e.__suppress_context__)
|
||||||
|
|
||||||
def testKeywordArgs(self):
|
def testKeywordArgs(self):
|
||||||
# test that builtin exception don't take keyword args,
|
# test that builtin exception don't take keyword args,
|
||||||
|
|
|
@ -174,11 +174,14 @@ class TestCause(unittest.TestCase):
|
||||||
raise ValueError from None
|
raise ValueError from None
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
self.assertIsNone(exc.__cause__)
|
self.assertIsNone(exc.__cause__)
|
||||||
raise exc from Ellipsis
|
self.assertTrue(exc.__suppress_context__)
|
||||||
|
exc.__suppress_context__ = False
|
||||||
|
raise exc
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
e = exc
|
e = exc
|
||||||
|
|
||||||
self.assertIs(e.__cause__, Ellipsis)
|
self.assertIsNone(e.__cause__)
|
||||||
|
self.assertFalse(e.__suppress_context__)
|
||||||
self.assertIsInstance(e.__context__, TypeError)
|
self.assertIsInstance(e.__context__, TypeError)
|
||||||
|
|
||||||
def test_invalid_cause(self):
|
def test_invalid_cause(self):
|
||||||
|
|
|
@ -700,14 +700,14 @@ class SizeofTest(unittest.TestCase):
|
||||||
class C(object): pass
|
class C(object): pass
|
||||||
check(C.__dict__, size(h + 'P'))
|
check(C.__dict__, size(h + 'P'))
|
||||||
# BaseException
|
# BaseException
|
||||||
check(BaseException(), size(h + '5P'))
|
check(BaseException(), size(h + '5Pi'))
|
||||||
# UnicodeEncodeError
|
# UnicodeEncodeError
|
||||||
check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5P 2P2PP'))
|
check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5Pi 2P2PP'))
|
||||||
# UnicodeDecodeError
|
# UnicodeDecodeError
|
||||||
# XXX
|
# XXX
|
||||||
# check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP'))
|
# check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP'))
|
||||||
# UnicodeTranslateError
|
# UnicodeTranslateError
|
||||||
check(UnicodeTranslateError("", 0, 1, ""), size(h + '5P 2P2PP'))
|
check(UnicodeTranslateError("", 0, 1, ""), size(h + '5Pi 2P2PP'))
|
||||||
# ellipses
|
# ellipses
|
||||||
check(Ellipsis, size(h + ''))
|
check(Ellipsis, size(h + ''))
|
||||||
# EncodingMap
|
# EncodingMap
|
||||||
|
|
|
@ -119,15 +119,16 @@ def _iter_chain(exc, custom_tb=None, seen=None):
|
||||||
seen = set()
|
seen = set()
|
||||||
seen.add(exc)
|
seen.add(exc)
|
||||||
its = []
|
its = []
|
||||||
|
context = exc.__context__
|
||||||
cause = exc.__cause__
|
cause = exc.__cause__
|
||||||
if cause is Ellipsis:
|
if cause is not None and cause not in seen:
|
||||||
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:
|
|
||||||
its.append(_iter_chain(cause, False, seen))
|
its.append(_iter_chain(cause, False, seen))
|
||||||
its.append([(_cause_message, None)])
|
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__)])
|
its.append([(exc, custom_tb or exc.__traceback__)])
|
||||||
# itertools.chain is in an extension module and may be unavailable
|
# itertools.chain is in an extension module and may be unavailable
|
||||||
for it in its:
|
for it in its:
|
||||||
|
|
|
@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 4?
|
||||||
Core and Builtins
|
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
|
- Issue #14417: Mutating a dict during lookup now restarts the lookup instead
|
||||||
of raising a RuntimeError (undoes issue #14205).
|
of raising a RuntimeError (undoes issue #14205).
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
/* the dict is created on the fly in PyObject_GenericSetAttr */
|
/* the dict is created on the fly in PyObject_GenericSetAttr */
|
||||||
self->dict = NULL;
|
self->dict = NULL;
|
||||||
self->traceback = self->cause = self->context = NULL;
|
self->traceback = self->cause = self->context = NULL;
|
||||||
|
self->suppress_context = 0;
|
||||||
|
|
||||||
self->args = PyTuple_New(0);
|
self->args = PyTuple_New(0);
|
||||||
if (!self->args) {
|
if (!self->args) {
|
||||||
|
@ -266,24 +267,7 @@ BaseException_get_cause(PyObject *self) {
|
||||||
PyObject *res = PyException_GetCause(self);
|
PyObject *res = PyException_GetCause(self);
|
||||||
if (res)
|
if (res)
|
||||||
return res; /* new reference already returned above */
|
return res; /* new reference already returned above */
|
||||||
Py_INCREF(Py_Ellipsis);
|
Py_RETURN_NONE;
|
||||||
return Py_Ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyException_SetCauseChecked(PyObject *self, PyObject *arg) {
|
|
||||||
if (arg == Py_Ellipsis) {
|
|
||||||
arg = NULL;
|
|
||||||
} else if (arg != Py_None && !PyExceptionInstance_Check(arg)) {
|
|
||||||
PyErr_SetString(PyExc_TypeError, "exception cause must be None, "
|
|
||||||
"Ellipsis or derive from BaseException");
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
/* PyException_SetCause steals a reference */
|
|
||||||
Py_INCREF(arg);
|
|
||||||
}
|
|
||||||
PyException_SetCause(self, arg);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -291,8 +275,18 @@ BaseException_set_cause(PyObject *self, PyObject *arg) {
|
||||||
if (arg == NULL) {
|
if (arg == NULL) {
|
||||||
PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
|
PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
|
||||||
return -1;
|
return -1;
|
||||||
|
} else if (arg == Py_None) {
|
||||||
|
arg = NULL;
|
||||||
|
} else if (!PyExceptionInstance_Check(arg)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "exception cause must be None "
|
||||||
|
"or derive from BaseException");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
/* PyException_SetCause steals this reference */
|
||||||
|
Py_INCREF(arg);
|
||||||
}
|
}
|
||||||
return _PyException_SetCauseChecked(self, arg);
|
PyException_SetCause(self, arg);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -333,6 +327,7 @@ void
|
||||||
PyException_SetCause(PyObject *self, PyObject *cause) {
|
PyException_SetCause(PyObject *self, PyObject *cause) {
|
||||||
PyObject *old_cause = ((PyBaseExceptionObject *)self)->cause;
|
PyObject *old_cause = ((PyBaseExceptionObject *)self)->cause;
|
||||||
((PyBaseExceptionObject *)self)->cause = cause;
|
((PyBaseExceptionObject *)self)->cause = cause;
|
||||||
|
((PyBaseExceptionObject *)self)->suppress_context = 1;
|
||||||
Py_XDECREF(old_cause);
|
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 = {
|
static PyTypeObject _PyExc_BaseException = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"BaseException", /*tp_name*/
|
"BaseException", /*tp_name*/
|
||||||
|
@ -382,7 +383,7 @@ static PyTypeObject _PyExc_BaseException = {
|
||||||
0, /* tp_iter */
|
0, /* tp_iter */
|
||||||
0, /* tp_iternext */
|
0, /* tp_iternext */
|
||||||
BaseException_methods, /* tp_methods */
|
BaseException_methods, /* tp_methods */
|
||||||
0, /* tp_members */
|
BaseException_members, /* tp_members */
|
||||||
BaseException_getset, /* tp_getset */
|
BaseException_getset, /* tp_getset */
|
||||||
0, /* tp_base */
|
0, /* tp_base */
|
||||||
0, /* tp_dict */
|
0, /* tp_dict */
|
||||||
|
|
|
@ -3572,23 +3572,26 @@ do_raise(PyObject *exc, PyObject *cause)
|
||||||
|
|
||||||
if (cause) {
|
if (cause) {
|
||||||
PyObject *fixed_cause;
|
PyObject *fixed_cause;
|
||||||
int result;
|
|
||||||
if (PyExceptionClass_Check(cause)) {
|
if (PyExceptionClass_Check(cause)) {
|
||||||
fixed_cause = PyObject_CallObject(cause, NULL);
|
fixed_cause = PyObject_CallObject(cause, NULL);
|
||||||
if (fixed_cause == NULL)
|
if (fixed_cause == NULL)
|
||||||
goto raise_error;
|
goto raise_error;
|
||||||
Py_CLEAR(cause);
|
Py_DECREF(cause);
|
||||||
} else {
|
|
||||||
/* Let "exc.__cause__ = cause" handle all further checks */
|
|
||||||
fixed_cause = cause;
|
|
||||||
cause = NULL; /* Steal the reference */
|
|
||||||
}
|
}
|
||||||
/* We retain ownership of the reference to fixed_cause */
|
else if (PyExceptionInstance_Check(cause)) {
|
||||||
result = _PyException_SetCauseChecked(value, fixed_cause);
|
fixed_cause = 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;
|
goto raise_error;
|
||||||
}
|
}
|
||||||
|
PyException_SetCause(value, fixed_cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyErr_SetObject(type, value);
|
PyErr_SetObject(type, value);
|
||||||
|
|
|
@ -1761,11 +1761,7 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
|
||||||
else if (PyExceptionInstance_Check(value)) {
|
else if (PyExceptionInstance_Check(value)) {
|
||||||
cause = PyException_GetCause(value);
|
cause = PyException_GetCause(value);
|
||||||
context = PyException_GetContext(value);
|
context = PyException_GetContext(value);
|
||||||
if (cause && cause == Py_None) {
|
if (cause) {
|
||||||
/* print neither cause nor context */
|
|
||||||
;
|
|
||||||
}
|
|
||||||
else if (cause) {
|
|
||||||
res = PySet_Contains(seen, cause);
|
res = PySet_Contains(seen, cause);
|
||||||
if (res == -1)
|
if (res == -1)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
|
@ -1776,7 +1772,8 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
|
||||||
cause_message, f);
|
cause_message, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context) {
|
else if (context &&
|
||||||
|
!((PyBaseExceptionObject *)value)->suppress_context) {
|
||||||
res = PySet_Contains(seen, context);
|
res = PySet_Contains(seen, context);
|
||||||
if (res == -1)
|
if (res == -1)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
|
|
Loading…
Reference in New Issue