[Python-Dev] PEP 409 update [was: PEP 409 - final?]
Ethan Furman
ethan at stoneleaf.us
Thu Feb 2 23:10:31 CET 2012
PEP: 409
Title: Suppressing exception context
Version: $Revision$
Last-Modified: $Date$
Author: Ethan Furman <ethan at stoneleaf.us>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 26-Jan-2012
Post-History: 30-Aug-2002, 01-Feb-2012, 03-Feb-2012
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 proprose 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
=========================
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 explicity 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. There are no other possible values; it cannot be raised as
it is not an acception; it has the connotation of 'fill in the
rest...' as in /__cause__/ is not set, look in /__context__/ for it.
Language Details
================
To support /from None/, /__context__/ will stay as it is, but
/__cause__/ will start out as /Ellipsis/ and will change to /None/
when the /raise ... from None/ method is used.
============================== ================== ==================
form __context__ __cause__
============================== ================== ==================
raise /None/ /Ellipsis/
reraise previous exception /Ellipsis/
reraise from previous exception /None/ |
/None/ | /ChainedException/ 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.
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:
http://mail.python.org/pipermail/python-dev/2012-January/115838.html
Copyright
=========
This document has been placed in the public domain.
More information about the Python-Dev
mailing list