[Python-checkins] cpython: PEP 415: Implement suppression of __context__ display with an exception

benjamin.peterson python-checkins at python.org
Tue May 15 07:09:55 CEST 2012


http://hg.python.org/cpython/rev/b0eb7d2a9542
changeset:   76942:b0eb7d2a9542
parent:      76939:6ff172db8114
user:        Benjamin Peterson <benjamin at python.org>
date:        Mon May 14 22:09:31 2012 -0700
summary:
  PEP 415: Implement suppression of __context__ display with an exception attribute

This replaces the original PEP 409 implementation. See #14133.

files:
  Doc/c-api/exceptions.rst    |   8 +----
  Doc/library/exceptions.rst  |  21 ++++++------
  Doc/library/stdtypes.rst    |   9 ++---
  Include/pyerrors.h          |   4 +-
  Lib/test/test_exceptions.py |  15 ++++----
  Lib/test/test_raise.py      |   7 +++-
  Lib/test/test_sys.py        |   6 +-
  Lib/traceback.py            |  13 ++++---
  Misc/NEWS                   |   3 +
  Objects/exceptions.c        |  41 ++++++++++++------------
  Python/ceval.c              |  21 +++++++-----
  Python/pythonrun.c          |   9 +---
  12 files changed, 79 insertions(+), 78 deletions(-)


diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -471,10 +471,6 @@
    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 @@
    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
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -39,18 +39,17 @@
 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`.)
+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.
+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
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -2996,11 +2996,10 @@
 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
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -10,7 +10,8 @@
 /* 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 @@
 /* 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
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -388,18 +388,18 @@
     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 @@
             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
--- a/Lib/test/test_raise.py
+++ b/Lib/test/test_raise.py
@@ -174,11 +174,14 @@
                     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
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -700,14 +700,14 @@
         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
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -119,15 +119,16 @@
         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
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 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
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -42,6 +42,7 @@
     /* 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,24 +267,7 @@
     PyObject *res = PyException_GetCause(self);
     if (res)
         return res;  /* new reference already returned above */
-    Py_INCREF(Py_Ellipsis);
-    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;
+    Py_RETURN_NONE;
 }
 
 static int
@@ -291,8 +275,18 @@
     if (arg == NULL) {
         PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
         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 @@
 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 @@
 }
 
 
+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 @@
     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
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3572,23 +3572,26 @@
 
     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
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -1761,11 +1761,7 @@
         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 @@
                         cause_message, f);
                 }
             }
-            else if (context) {
+            else if (context &&
+                !((PyBaseExceptionObject *)value)->suppress_context) {
                 res = PySet_Contains(seen, context);
                 if (res == -1)
                     PyErr_Clear();

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list