207 lines
6.1 KiB
Plaintext
207 lines
6.1 KiB
Plaintext
PEP: 409
|
||
Title: Suppressing exception context
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Ethan Furman <ethan@stoneleaf.us>
|
||
Status: Final
|
||
Type: Standards Track
|
||
Content-Type: text/x-rst
|
||
Created: 26-Jan-2012
|
||
Post-History: 30-Aug-2002, 01-Feb-2012, 03-Feb-2012
|
||
Superseded-By: 415
|
||
Resolution: https://mail.python.org/pipermail/python-dev/2012-February/116136.html
|
||
|
||
|
||
Abstract
|
||
========
|
||
|
||
One of the open issues from PEP 3134 is suppressing context: currently
|
||
there is no way to do it. This PEP proposes one.
|
||
|
||
|
||
Rationale
|
||
=========
|
||
|
||
There are two basic ways to generate exceptions:
|
||
|
||
1) Python does it (buggy code, missing resources, ending loops, etc.)
|
||
|
||
2) manually (with a raise statement)
|
||
|
||
When writing libraries, or even just custom classes, it can become
|
||
necessary to raise exceptions; moreover it can be useful, even
|
||
necessary, to change from one exception to another. To take an example
|
||
from my dbf module::
|
||
|
||
try:
|
||
value = int(value)
|
||
except Exception:
|
||
raise DbfError(...)
|
||
|
||
Whatever the original exception was (``ValueError``, ``TypeError``, or
|
||
something else) is irrelevant. The exception from this point on is a
|
||
``DbfError``, and the original exception is of no value. However, if
|
||
this exception is printed, we would currently see both.
|
||
|
||
|
||
Alternatives
|
||
============
|
||
Several possibilities have been put forth:
|
||
|
||
* ``raise as NewException()``
|
||
|
||
Reuses the ``as`` keyword; can be confusing since we are not really
|
||
reraising the originating exception
|
||
|
||
* ``raise NewException() from None``
|
||
|
||
Follows existing syntax of explicitly declaring the originating
|
||
exception
|
||
|
||
* ``exc = NewException(); exc.__context__ = None; raise exc``
|
||
|
||
Very verbose way of the previous method
|
||
|
||
* ``raise NewException.no_context(...)``
|
||
|
||
Make context suppression a class method.
|
||
|
||
All of the above options will require changes to the core.
|
||
|
||
|
||
Proposal
|
||
========
|
||
|
||
I propose going with the second option::
|
||
|
||
raise NewException from None
|
||
|
||
It has the advantage of using the existing pattern of explicitly setting
|
||
the cause::
|
||
|
||
raise KeyError() from NameError()
|
||
|
||
but because the cause is ``None`` the previous context is not displayed
|
||
by the default exception printing routines.
|
||
|
||
|
||
Implementation Discussion
|
||
=========================
|
||
|
||
Note: after acceptance of this PEP, a cleaner implementation mechanism
|
||
was proposed and accepted in PEP 415. Refer to that PEP for more
|
||
details on the implementation actually used in Python 3.3.
|
||
|
||
Currently, ``None`` is the default for both ``__context__`` and ``__cause__``.
|
||
In order to support ``raise ... from None`` (which would set ``__cause__`` to
|
||
``None``) we need a different default value for ``__cause__``. Several ideas
|
||
were put forth on how to implement this at the language level:
|
||
|
||
* Overwrite the previous exception information (side-stepping the issue and
|
||
leaving ``__cause__`` at ``None``).
|
||
|
||
Rejected as this can seriously hinder debugging due to
|
||
`poor error messages`_.
|
||
|
||
* Use one of the boolean values in ``__cause__``: ``False`` would be the
|
||
default value, and would be replaced when ``from ...`` was used with the
|
||
explicitly chained exception or ``None``.
|
||
|
||
Rejected as this encourages the use of two different objects types for
|
||
``__cause__`` with one of them (boolean) not allowed to have the full range
|
||
of possible values (``True`` would never be used).
|
||
|
||
* Create a special exception class, ``__NoException__``.
|
||
|
||
Rejected as possibly confusing, possibly being mistakenly raised by users,
|
||
and not being a truly unique value as ``None``, ``True``, and ``False`` are.
|
||
|
||
* Use ``Ellipsis`` as the default value (the ``...`` singleton).
|
||
|
||
Accepted.
|
||
|
||
Ellipses are commonly used in English as place holders when words are
|
||
omitted. This works in our favor here as a signal that ``__cause__`` is
|
||
omitted, so look in ``__context__`` for more details.
|
||
|
||
Ellipsis is not an exception, so cannot be raised.
|
||
|
||
There is only one Ellipsis, so no unused values.
|
||
|
||
Error information is not thrown away, so custom code can trace the entire
|
||
exception chain even if the default code does not.
|
||
|
||
|
||
Language Details
|
||
================
|
||
|
||
To support ``raise Exception from None``, ``__context__`` will stay as it is,
|
||
but ``__cause__`` will start out as ``Ellipsis`` and will change to ``None``
|
||
when the ``raise Exception from None`` method is used.
|
||
|
||
============================================ ================== =======================================
|
||
form __context__ __cause__
|
||
============================================ ================== =======================================
|
||
raise ``None`` ``Ellipsis``
|
||
reraise previous exception ``Ellipsis``
|
||
reraise from ``None`` | ``ChainedException`` previous exception ``None`` | explicitly chained exception
|
||
============================================ ================== =======================================
|
||
|
||
The default exception printing routine will then:
|
||
|
||
* If ``__cause__`` is ``Ellipsis`` the ``__context__`` (if any) will be
|
||
printed.
|
||
|
||
* If ``__cause__`` is ``None`` the ``__context__`` will not be printed.
|
||
|
||
* if ``__cause__`` is anything else, ``__cause__`` will be printed.
|
||
|
||
In both of the latter cases the exception chain will stop being followed.
|
||
|
||
Because the default value for ``__cause__`` is now ``Ellipsis`` and ``raise
|
||
Exception from Cause`` is simply syntactic sugar for::
|
||
|
||
_exc = NewException()
|
||
_exc.__cause__ = Cause()
|
||
raise _exc
|
||
|
||
``Ellipsis``, as well as ``None``, is now allowed as a cause::
|
||
|
||
raise Exception from Ellipsis
|
||
|
||
|
||
Patches
|
||
=======
|
||
|
||
There is a patch for CPython implementing this attached to `Issue 6210`_.
|
||
|
||
|
||
References
|
||
==========
|
||
|
||
Discussion and refinements in this `thread on python-dev`_.
|
||
|
||
.. _poor error messages:
|
||
http://bugs.python.org/msg152294
|
||
.. _issue 6210:
|
||
http://bugs.python.org/issue6210
|
||
.. _Thread on python-dev:
|
||
https://mail.python.org/pipermail/python-dev/2012-January/115838.html
|
||
|
||
|
||
Copyright
|
||
=========
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
..
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
sentence-end-double-space: t
|
||
fill-column: 70
|
||
coding: utf-8
|
||
End:
|
||
|