Add PEP 344: Exception Chaining and the Traceback Attribute.
This commit is contained in:
parent
5c42550605
commit
f9e92a3824
|
@ -0,0 +1,359 @@
|
||||||
|
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.
|
Loading…
Reference in New Issue