python-peps/pep-0344.txt

360 lines
13 KiB
Plaintext
Raw Blame History

PEP: 344
Title: Exception Chaining and the Traceback Attribute
Version: $Revision$
Last-Modified: $Date$
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.