[Python-checkins] python/nondist/peps pep-0344.txt,NONE,1.1

ping@users.sourceforge.net ping at users.sourceforge.net
Sun May 15 21:30:41 CEST 2005


Update of /cvsroot/python/python/nondist/peps
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv18411

Added Files:
	pep-0344.txt 
Log Message:
Add PEP 344: Exception Chaining and the Traceback Attribute.


--- NEW FILE: pep-0344.txt ---
PEP: 344
Title: Exception Chaining and the Traceback Attribute
Version: $Revision: 1.1 $
Last-Modified: $Date: 2005/05/15 19:30:38 $
Author: Ka-Ping Yee
Status: Active
Type: Standards Track
Content-Type: text/plain
Created: 12-May-2005
Python-Version: 2.5


Abstract

    This PEP proposes two standard attributes on exception instances:
    the 'context' attribute for chained exceptions, and the 'traceback'
    attribute for the traceback.


Motivation

    Sometimes, during the handling of one exception (exception A),
    another exception (exception B) can occur.  In today's Python
    (version 2.4), if this happens, exception B is propagated outward
    and exception A is lost.  But in order to debug the problem, it is
    useful to know about both exceptions.  The 'context' attribute
    retains this information.

    In today's Python implementation, exceptions are composed of three
    parts: the type, the value, and the traceback.  The 'sys' module,
    exposes the current exception in three parallel variables, exc_type,
    exc_value, and exc_traceback, the sys.exc_info() function returns a
    tuple of these three parts, and the 'raise' statement has a
    three-argument form accepting these three parts.  Manipulating
    exceptions often requires passing these three things in parallel,
    which can be tedious and error-prone.  Additionally, the 'except'
    statement can only provide access to the value, not the traceback.
    Adding the 'traceback' attribute to exception values makes all the
    exception information accessible from a single place.

    The reason both of these attributes are presented together in one
    proposal is that the 'traceback' attribute provides convenient
    access to the traceback on chained exceptions.


History

    Raymond Hettinger [1] raised the issue of masked exceptions on
    Python-Dev in January 2003 and proposed a PyErr_FormatAppend()
    function that C modules could use to augment the currently active
    exception with more information.

    Brett Cannon [2] brought up chained exceptions again in June 2003
    and a long discussion followed.  Other suggested attribute names
    include 'cause', 'antecedent', 'reason', 'original', 'chain',
    'chainedexc', 'exc_chain', 'excprev', 'previous', and 'precursor'.
    This PEP suggests 'context' because the intended meaning is more
    specific than temporal precedence and less specific than causation:
    an exception occurs in the *context* of handling another exception.

    Greg Ewing [3] identified the case of an exception occuring in a
    'finally' block during unwinding triggered by an original exception,
    as distinct from the case of an exception occuring in an 'except'
    block that is handling the original exception.  This PEP handles
    both situations in the same way; it is assumed to be unnecessary to
    add mechanisms for distinguishing them since the programmer can tell
    them apart by reading the traceback.

    Greg Ewing [4] and Guido van Rossum [5], and probably others, have
    previously mentioned adding the traceback attribute to Exception
    instances.  This is noted in PEP 3000.

    This PEP was motivated by yet another recent Python-Dev reposting
    of the same ideas [6] [7].


*** Add rationale for: choice of name, handling both finally/except,
    handling finally/except the same, displaying outermost last.

*** Compare to Java: exceptions are lost in catch or finally clauses.

*** Compare to Ruby: exceptions are lost just like Java.

*** Compare to C#:

*** Compare to E:

*** COmpare to Perl: RFC 88 a mess.

*** Note http://pclt.cis.yale.edu/pclt/exceptions.htm


Exception Chaining

    Here is an example to illustrate the 'context' attribute.

        def compute(a, b):
            try:
                a/b
            except Exception, exc:
                log(exc)

        def log(exc):
            file = open('logfile.txt')  # oops, forgot the 'w'
            print >>file, exc
            file.close()

    Calling compute(0, 0) causes a ZeroDivisionError.  The compute()
    function catches this exception and calls log(exc), but the log()
    function also raises an exception when it tries to write to a
    file that wasn't opened for writing.

    In today's Python, the caller of compute() gets thrown an IOError.
    The ZeroDivisionError is lost.  With the proposed change, the
    instance of IOError has an additional 'context' attribute that
    retains the ZeroDivisionError.

    The following more elaborate example demonstrates the handling of a
    mix of 'finally' and 'except' clauses:

        def main(filename):
            file = open(filename)       # oops, forgot the 'w'
            try:
                try:
                    compute()
                except Exception, exc:
                    log(file, exc)
            finally:
                file.clos()             # oops, misspelled 'close'
        
        def compute():
            1/0
        
        def log(file, exc):
            try:
                print >>file, exc       # oops, file is not writable
            except:
                display(exc)
        
        def display(exc):
            print ex                    # oops, misspelled 'exc'

    Calling main() with the name of an existing file will trigger four
    exceptions.  The ultimate result will be an AttributeError due to
    the misspelling of 'clos', which has a context attribute pointing to
    a NameError due to the misspelling of 'ex', which has a context
    attribute pointing to an IOError due to the file being read-only,
    which has a context attribute pointing to a ZeroDivisionError, which
    has a context attribute of None.

    The proposed semantics are as follows:

    1.  Each thread has an exception context initially set to None.
    
    2.  Whenever an exception is raised, if the exception instance does
        not already have a 'context' attribute, the interpreter sets it
        equal to the thread's exception context.

    3.  Immediately after an exception is raised, the thread's exception
        context is set to the exception.

    4.  Whenever the interpreter exits an 'except' block by reaching the
        end or executing a 'return', 'yield', 'continue', or 'break'
        statement, the thread's exception context is set to None.


Traceback Attribute

    The following example illustrates the 'traceback' attribute.

        def do_logged(file, work):
            try:
                work()
            except Exception, exc:
                write_exception(file, exc)
                raise exc

        from traceback import format_tb

        def write_exception(file, exc):
            ...
            type = exc.__class__
            message = str(exc)
            lines = format_tb(exc.traceback)
            file.write(... type ... message ... lines ...)
            ...

    In today's Python, the do_logged() function would have to extract
    the traceback from sys.exc_traceback or sys.exc_info()[2] and pass
    both the value and the traceback to write_exception().  With the
    proposed change, write_exception() simply gets one argument and
    obtains the exception using the 'traceback' attribute.

    The proposed semantics are as follows:

    1.  Whenever an exception is raised, if the exception instance does
        not already have a 'traceback' attribute, the interpreter sets
        it to the newly raised traceback.


Enhanced Reporting

    The default exception handler will be modified to report chained
    exceptions.  In keeping with the chronological order of tracebacks,
    the most recently raised exception is displayed last.  The display
    begins with the description of the innermost exception and backs
    up the chain to the outermost exception.  The tracebacks are
    formatted as usual, with the following line between tracebacks:

        During handling of the above exception, another exception occurred:

    In the 'traceback' module, the format_exception, print_exception,
    print_exc, and print_last functions will be updated to accept an
    optional 'context' argument, True by default.  When this argument is
    True, these functions will format or display the entire chain of
    exceptions as just described.  When it is False, these functions
    will format or display only the outermost exception.

    The 'cgitb' module will be updated to display the entire chain of
    exceptions.


C API

    To keep things simpler, the PyErr_Set* calls for setting exceptions
    will not set the 'context' attribute on exceptions.  Guido van Rossum
    has expressed qualms with making such changes to PyErr_Set* [8].

    PyErr_NormalizeException will always set the 'traceback' attribute
    to its 'tb' argument and the 'context' attribute to None.

    A new API function, PyErr_SetContext(context), will help C
    programmers provide chained exception information.  This function
    will first normalize the current exception so it is an instance,
    then set its 'context' attribute.


Compatibility

    Chained exceptions expose their outermost type so that they will
    continue to match the same 'except' clauses as they do now.

    The proposed changes should not break any code except for code
    that currently sets and relies on the values of attributes named
    'context' or 'traceback' on exceptions.

    As of 2005-05-12, the Python standard library contains no mention
    of such attributes.


Open Issues

    Walter Dörwald [9] expressed a desire to attach extra information
    to an exception during its upward propagation, without changing its
    type.  This could be a useful feature, but it is not addressed by
    this PEP.  It could conceivably be addressed by a separate PEP
    establishing conventions for other informational attributes on
    exceptions.

    It is not clear whether the 'context' feature proposed here would
    be sufficient to cover all the use cases that Raymond Hettinger [1]
    originally had in mind.

    The exception context is lost when a 'yield' statement is executed;
    resuming the frame after the 'yield' does not restore the context.
    This is not a new problem, as demonstrated by the following example:

        >>> def gen():
        ...     try:
        ...         1/0
        ...     except:
        ...         yield 3
        ...         raise
        ...
        >>> g = gen()
        >>> g.next()
        3
        >>> g.next()
        TypeError: exceptions must be classes, instances, or strings
        (deprecated), not NoneType

    For now, addressing this problem is out of the scope of this PEP.


Possible Future Compatible Changes

    These changes are consistent with the appearance of exceptions as
    a single object rather than a triple at the interpreter level.

    - Deprecating sys.exc_type, sys.exc_value, sys.exc_traceback, and
      sys.exc_info() in favour of a single member, sys.exception.

    - Deprecating sys.last_type, sys.last_value, and sys.last_traceback
      in favour of a single member, sys.last_exception.

    - Deprecating the three-argument form of the 'raise' statement in
      favour of the one-argument form.

    - Upgrading cgitb.html() to accept a single value as its first
      argument as an alternative to a (type, value, traceback) tuple.


Possible Future Incompatible Changes

    These changes might be worth considering for Python 3000.

    - Removing sys.exc_type, sys.exc_value, sys.exc_traceback, and
      sys.exc_info().

    - Removing sys.last_type, sys.last_value, and sys.last_traceback.

    - Replacing the three-argument sys.excepthook with a one-argument
      API, and changing the 'cgitb' module to match.

    - Removing the three-argument form of the 'raise' statement.

    - Upgrading traceback.print_exception to accept an 'exception'
      argument instead of the type, value, and traceback arguments.


Acknowledgements

    Brett Cannon, Greg Ewing, Guido van Rossum, Jeremy Hylton, Phillip
    J. Eby, Raymond Hettinger, Walter Dörwald, and others.


References

    [1] Raymond Hettinger, "Idea for avoiding exception masking"
        http://mail.python.org/pipermail/python-dev/2003-January/032492.html

    [2] Brett Cannon explains chained exceptions
        http://mail.python.org/pipermail/python-dev/2003-June/036063.html

    [3] Greg Ewing points out masking caused by exceptions during finally
        http://mail.python.org/pipermail/python-dev/2003-June/036290.html

    [4] Greg Ewing suggests storing the traceback in the exception object
        http://mail.python.org/pipermail/python-dev/2003-June/036092.html

    [5] Guido van Rossum mentions exceptions having a traceback attribute
        http://mail.python.org/pipermail/python-dev/2005-April/053060.html

    [6] Ka-Ping Yee, "Tidier Exceptions"
        http://mail.python.org/pipermail/python-dev/2005-May/053671.html

    [7] Ka-Ping Yee, "Chained Exceptions"
        http://mail.python.org/pipermail/python-dev/2005-May/053672.html

    [8] Guido van Rossum discusses automatic chaining in PyErr_Set*
        http://mail.python.org/pipermail/python-dev/2003-June/036180.html
     
    [9] Walter Dörwald suggests wrapping exceptions to add details
        http://mail.python.org/pipermail/python-dev/2003-June/036148.html


Copyright

    This document has been placed in the public domain.



More information about the Python-checkins mailing list