python-peps/peps/pep-0409.rst

208 lines
6.1 KiB
ReStructuredText
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
Python-Version: 3.3
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: