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