Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d5a1c44

Browse files
committed
PEP 415: Implement suppression of __context__ display with an exception attribute
This replaces the original PEP 409 implementation. See #14133.
1 parent d91dc62 commit d5a1c44

12 files changed

Lines changed: 79 additions & 78 deletions

File tree

Doc/c-api/exceptions.rst

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -471,20 +471,14 @@ Exception Objects
471471
set by ``raise ... from ...``) associated with the exception as a new
472472
reference, as accessible from Python through :attr:`__cause__`.
473473
474-
If there is no cause associated, this returns *NULL* (from Python
475-
``__cause__ is Ellipsis``). If the cause is :const:`None`, the default
476-
exception display routines stop showing the context chain.
477-
478474
479475
.. c:function:: void PyException_SetCause(PyObject *ex, PyObject *ctx)
480476
481477
Set the cause associated with the exception to *ctx*. Use *NULL* to clear
482478
it. There is no type check to make sure that *ctx* is either an exception
483479
instance or :const:`None`. This steals a reference to *ctx*.
484480
485-
If the cause is set to :const:`None` the default exception display
486-
routines will not display this exception's context, and will not follow the
487-
chain any further.
481+
:attr:`__suppress_context__` is implicitly set to ``True`` by this function.
488482
489483
490484
.. _unicodeexceptions:

Doc/library/exceptions.rst

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,17 @@ When raising (or re-raising) an exception in an :keyword:`except` clause
3939
new exception is not handled the traceback that is eventually displayed will
4040
include the originating exception(s) and the final exception.
4141

42-
This implicit exception chain can be made explicit by using :keyword:`from`
43-
with :keyword:`raise`. The single argument to :keyword:`from` must be an
44-
exception or :const:`None`, and it will be set as :attr:`__cause__` on the
45-
raised exception. If :attr:`__cause__` is an exception it will be displayed
46-
instead of :attr:`__context__`; if :attr:`__cause__` is None,
47-
:attr:`__context__` will not be displayed by the default exception handling
48-
code. (Note: the default value for :attr:`__context__` is :const:`None`,
49-
while the default value for :attr:`__cause__` is :const:`Ellipsis`.)
50-
51-
In either case, the default exception handling code will not display
52-
any of the remaining links in the :attr:`__context__` chain if
53-
:attr:`__cause__` has been set.
42+
This implicit exception chain can be made explicit by using :keyword:`from` with
43+
:keyword:`raise`. The single argument to :keyword:`from` must be an exception
44+
or ``None``. It will be set as :attr:`__cause__` on the raised exception.
45+
Setting :attr:`__cause__` implicitly sets the :attr:`__suppress_context__` to
46+
``True``. If :attr:`__cause__` is an exception, it will be displayed. If
47+
:attr:`__cause__` is present or :attr:`__suppress_context__` has a true value,
48+
:attr:`__context__` will not be displayed.
49+
50+
In either case, the default exception handling code will not display any of the
51+
remaining links in the :attr:`__context__` chain if :attr:`__cause__` has been
52+
set.
5453

5554

5655
Base classes

Doc/library/stdtypes.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,11 +2996,10 @@ It is written as ``None``.
29962996
The Ellipsis Object
29972997
-------------------
29982998

2999-
This object is commonly used by slicing (see :ref:`slicings`), but may also
3000-
be used in other situations where a sentinel value other than :const:`None`
3001-
is needed. It supports no special operations. There is exactly one ellipsis
3002-
object, named :const:`Ellipsis` (a built-in name). ``type(Ellipsis)()``
3003-
produces the :const:`Ellipsis` singleton.
2999+
This object is commonly used by slicing (see :ref:`slicings`). It supports no
3000+
special operations. There is exactly one ellipsis object, named
3001+
:const:`Ellipsis` (a built-in name). ``type(Ellipsis)()`` produces the
3002+
:const:`Ellipsis` singleton.
30043003

30053004
It is written as ``Ellipsis`` or ``...``.
30063005

Include/pyerrors.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ extern "C" {
1010
/* PyException_HEAD defines the initial segment of every exception class. */
1111
#define PyException_HEAD PyObject_HEAD PyObject *dict;\
1212
PyObject *args; PyObject *traceback;\
13-
PyObject *context; PyObject *cause;
13+
PyObject *context; PyObject *cause;\
14+
int suppress_context;
1415

1516
typedef struct {
1617
PyException_HEAD
@@ -114,7 +115,6 @@ PyAPI_FUNC(PyObject *) PyException_GetTraceback(PyObject *);
114115
/* Cause manipulation (PEP 3134) */
115116
PyAPI_FUNC(PyObject *) PyException_GetCause(PyObject *);
116117
PyAPI_FUNC(void) PyException_SetCause(PyObject *, PyObject *);
117-
PyAPI_FUNC(int) _PyException_SetCauseChecked(PyObject *, PyObject *);
118118

119119
/* Context manipulation (PEP 3134) */
120120
PyAPI_FUNC(PyObject *) PyException_GetContext(PyObject *);

Lib/test/test_exceptions.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -388,18 +388,18 @@ def testNoneClearsTracebackAttr(self):
388388
def testChainingAttrs(self):
389389
e = Exception()
390390
self.assertIsNone(e.__context__)
391-
self.assertIs(e.__cause__, Ellipsis)
391+
self.assertIsNone(e.__cause__)
392392

393393
e = TypeError()
394394
self.assertIsNone(e.__context__)
395-
self.assertIs(e.__cause__, Ellipsis)
395+
self.assertIsNone(e.__cause__)
396396

397397
class MyException(EnvironmentError):
398398
pass
399399

400400
e = MyException()
401401
self.assertIsNone(e.__context__)
402-
self.assertIs(e.__cause__, Ellipsis)
402+
self.assertIsNone(e.__cause__)
403403

404404
def testChainingDescriptors(self):
405405
try:
@@ -408,15 +408,16 @@ def testChainingDescriptors(self):
408408
e = exc
409409

410410
self.assertIsNone(e.__context__)
411-
self.assertIs(e.__cause__, Ellipsis)
411+
self.assertIsNone(e.__cause__)
412+
self.assertFalse(e.__suppress_context__)
412413

413414
e.__context__ = NameError()
414415
e.__cause__ = None
415416
self.assertIsInstance(e.__context__, NameError)
416417
self.assertIsNone(e.__cause__)
417-
418-
e.__cause__ = Ellipsis
419-
self.assertIs(e.__cause__, Ellipsis)
418+
self.assertTrue(e.__suppress_context__)
419+
e.__suppress_context__ = False
420+
self.assertFalse(e.__suppress_context__)
420421

421422
def testKeywordArgs(self):
422423
# test that builtin exception don't take keyword args,

Lib/test/test_raise.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,14 @@ def testCauseSyntax(self):
174174
raise ValueError from None
175175
except ValueError as exc:
176176
self.assertIsNone(exc.__cause__)
177-
raise exc from Ellipsis
177+
self.assertTrue(exc.__suppress_context__)
178+
exc.__suppress_context__ = False
179+
raise exc
178180
except ValueError as exc:
179181
e = exc
180182

181-
self.assertIs(e.__cause__, Ellipsis)
183+
self.assertIsNone(e.__cause__)
184+
self.assertFalse(e.__suppress_context__)
182185
self.assertIsInstance(e.__context__, TypeError)
183186

184187
def test_invalid_cause(self):

Lib/test/test_sys.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,14 +700,14 @@ def inner():
700700
class C(object): pass
701701
check(C.__dict__, size(h + 'P'))
702702
# BaseException
703-
check(BaseException(), size(h + '5P'))
703+
check(BaseException(), size(h + '5Pi'))
704704
# UnicodeEncodeError
705-
check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5P 2P2PP'))
705+
check(UnicodeEncodeError("", "", 0, 0, ""), size(h + '5Pi 2P2PP'))
706706
# UnicodeDecodeError
707707
# XXX
708708
# check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP'))
709709
# UnicodeTranslateError
710-
check(UnicodeTranslateError("", 0, 1, ""), size(h + '5P 2P2PP'))
710+
check(UnicodeTranslateError("", 0, 1, ""), size(h + '5Pi 2P2PP'))
711711
# ellipses
712712
check(Ellipsis, size(h + ''))
713713
# EncodingMap

Lib/traceback.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,16 @@ def _iter_chain(exc, custom_tb=None, seen=None):
119119
seen = set()
120120
seen.add(exc)
121121
its = []
122+
context = exc.__context__
122123
cause = exc.__cause__
123-
if cause is Ellipsis:
124-
context = exc.__context__
125-
if context is not None and context not in seen:
126-
its.append(_iter_chain(context, None, seen))
127-
its.append([(_context_message, None)])
128-
elif cause is not None and cause not in seen:
124+
if cause is not None and cause not in seen:
129125
its.append(_iter_chain(cause, False, seen))
130126
its.append([(_cause_message, None)])
127+
elif (context is not None and
128+
not exc.__suppress_context__ and
129+
context not in seen):
130+
its.append(_iter_chain(context, None, seen))
131+
its.append([(_context_message, None)])
131132
its.append([(exc, custom_tb or exc.__traceback__)])
132133
# itertools.chain is in an extension module and may be unavailable
133134
for it in its:

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 4?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #14133 (PEP 415): Implement suppression of __context__ display with an
14+
attribute on BaseException. This replaces the original mechanism of PEP 409.
15+
1316
- Issue #14417: Mutating a dict during lookup now restarts the lookup instead
1417
of raising a RuntimeError (undoes issue #14205).
1518

Objects/exceptions.c

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
4242
/* the dict is created on the fly in PyObject_GenericSetAttr */
4343
self->dict = NULL;
4444
self->traceback = self->cause = self->context = NULL;
45+
self->suppress_context = 0;
4546

4647
self->args = PyTuple_New(0);
4748
if (!self->args) {
@@ -266,35 +267,28 @@ BaseException_get_cause(PyObject *self) {
266267
PyObject *res = PyException_GetCause(self);
267268
if (res)
268269
return res; /* new reference already returned above */
269-
Py_INCREF(Py_Ellipsis);
270-
return Py_Ellipsis;
270+
Py_RETURN_NONE;
271271
}
272272

273-
int
274-
_PyException_SetCauseChecked(PyObject *self, PyObject *arg) {
275-
if (arg == Py_Ellipsis) {
273+
static int
274+
BaseException_set_cause(PyObject *self, PyObject *arg) {
275+
if (arg == NULL) {
276+
PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
277+
return -1;
278+
} else if (arg == Py_None) {
276279
arg = NULL;
277-
} else if (arg != Py_None && !PyExceptionInstance_Check(arg)) {
278-
PyErr_SetString(PyExc_TypeError, "exception cause must be None, "
279-
"Ellipsis or derive from BaseException");
280+
} else if (!PyExceptionInstance_Check(arg)) {
281+
PyErr_SetString(PyExc_TypeError, "exception cause must be None "
282+
"or derive from BaseException");
280283
return -1;
281284
} else {
282-
/* PyException_SetCause steals a reference */
285+
/* PyException_SetCause steals this reference */
283286
Py_INCREF(arg);
284287
}
285288
PyException_SetCause(self, arg);
286289
return 0;
287290
}
288291

289-
static int
290-
BaseException_set_cause(PyObject *self, PyObject *arg) {
291-
if (arg == NULL) {
292-
PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
293-
return -1;
294-
}
295-
return _PyException_SetCauseChecked(self, arg);
296-
}
297-
298292

299293
static PyGetSetDef BaseException_getset[] = {
300294
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
@@ -333,6 +327,7 @@ void
333327
PyException_SetCause(PyObject *self, PyObject *cause) {
334328
PyObject *old_cause = ((PyBaseExceptionObject *)self)->cause;
335329
((PyBaseExceptionObject *)self)->cause = cause;
330+
((PyBaseExceptionObject *)self)->suppress_context = 1;
336331
Py_XDECREF(old_cause);
337332
}
338333

@@ -352,6 +347,12 @@ PyException_SetContext(PyObject *self, PyObject *context) {
352347
}
353348

354349

350+
static struct PyMemberDef BaseException_members[] = {
351+
{"__suppress_context__", T_BOOL,
352+
offsetof(PyBaseExceptionObject, suppress_context)}
353+
};
354+
355+
355356
static PyTypeObject _PyExc_BaseException = {
356357
PyVarObject_HEAD_INIT(NULL, 0)
357358
"BaseException", /*tp_name*/
@@ -382,7 +383,7 @@ static PyTypeObject _PyExc_BaseException = {
382383
0, /* tp_iter */
383384
0, /* tp_iternext */
384385
BaseException_methods, /* tp_methods */
385-
0, /* tp_members */
386+
BaseException_members, /* tp_members */
386387
BaseException_getset, /* tp_getset */
387388
0, /* tp_base */
388389
0, /* tp_dict */

0 commit comments

Comments
 (0)