[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