PEP: 409
Title: Suppressing exception context
Version: $Revision$
Last-Modified: $Date$
Author: Ethan Furman <et...@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.

_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to