Add __cause__ and language comparisons.
This commit is contained in:
parent
c2c4af5fd7
commit
43671683c9
250
pep-0344.txt
250
pep-0344.txt
|
@ -12,19 +12,25 @@ 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.
|
||||
This PEP proposes three standard attributes on exception instances:
|
||||
the '__context__' attribute for implicitly chained exceptions, the
|
||||
'__cause__' attribute for explicitly 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
|
||||
During the handling of one exception (exception A), it is possible
|
||||
that another exception (exception B) may 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.
|
||||
and exception A is lost. In order to debug the problem, it is
|
||||
useful to know about both exceptions. The '__context__' attribute
|
||||
retains this information automatically.
|
||||
|
||||
Sometimes it can be useful for an exception handler to intentionally
|
||||
re-raise an exception, either to provide extra information or to
|
||||
translate an exception to another type. The '__cause__' attribute
|
||||
provides an explicit way to record the direct cause of an exception.
|
||||
|
||||
In today's Python implementation, exceptions are composed of three
|
||||
parts: the type, the value, and the traceback. The 'sys' module,
|
||||
|
@ -35,12 +41,8 @@ Motivation
|
|||
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.
|
||||
Adding the '__traceback__' attribute to exception values makes all
|
||||
the exception information accessible from a single place.
|
||||
|
||||
|
||||
History
|
||||
|
@ -48,51 +50,83 @@ 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.
|
||||
exception with more information. Brett Cannon [2] brought up
|
||||
chained exceptions again in June 2003, prompting a long discussion.
|
||||
|
||||
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.
|
||||
block that is handling the original exception.
|
||||
|
||||
Greg Ewing [4] and Guido van Rossum [5], and probably others, have
|
||||
previously mentioned adding the traceback attribute to Exception
|
||||
previously mentioned adding a 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.
|
||||
Rationale
|
||||
|
||||
*** Compare to Java: exceptions are lost in catch or finally clauses.
|
||||
This PEP distinguishes implicit chaining from explicit chaining of
|
||||
exceptions because the unexpected raising of a secondary exception
|
||||
and the intentional translation of an exception are two different
|
||||
situations deserving quite different interpretations.
|
||||
|
||||
*** Compare to Ruby: exceptions are lost just like Java.
|
||||
Several attribute names for chained exceptions have been suggested
|
||||
on Python-Dev [2], including 'cause', 'antecedent', 'reason',
|
||||
'original', 'chain', 'chainedexc', 'exc_chain', 'excprev',
|
||||
'previous', and 'precursor'. For an explicitly chained exception,
|
||||
this PEP suggests '__cause__' because of its specific meaning. For
|
||||
an implicitly chained exception, this PEP proposes the name
|
||||
'__context__' because the intended meaning is more specific than
|
||||
temporal precedence but less specific than causation: an exception
|
||||
occurs in the context of handling another exception.
|
||||
|
||||
This PEP suggests names with leading and trailing double-underscores
|
||||
for '__context__' and '__traceback__' because the attributes are set
|
||||
by the Python VM. The name '__cause__' is not set automatically by
|
||||
the VM, but it seems confusing and collision-prone to use 'cause'.
|
||||
|
||||
*** Compare to C#:
|
||||
This PEP handles exceptions that occur during 'except' blocks and
|
||||
'finally' blocks in the same way. Reading the traceback makes it
|
||||
clear where the exceptions occurred, so additional mechanisms for
|
||||
distinguishing the two cases would only add unnecessary complexity.
|
||||
|
||||
*** Compare to E:
|
||||
This PEP proposes that the outermost exception object (the one
|
||||
exposed for matching by 'except' clauses) be the most recently
|
||||
raised exception for compatibility with current behaviour.
|
||||
|
||||
*** COmpare to Perl: RFC 88 a mess.
|
||||
This PEP proposes that tracebacks display the outermost exception
|
||||
last, because it would be consistent with the chronological order
|
||||
of tracebacks (from oldest to most recent frame) and because the
|
||||
actual thrown exception is easier to find on the last line.
|
||||
|
||||
*** Note http://pclt.cis.yale.edu/pclt/exceptions.htm
|
||||
Java and Ruby both discard the current exception when an exception
|
||||
occurs in a 'catch'/'rescue' or 'finally'/'ensure' clause. Perl 5
|
||||
lacks built-in structured exception handling. For Perl 6, RFC 88
|
||||
proposes an exception mechanism that retains all the chained
|
||||
exceptions in an array named @@. In that RFC, the most recently
|
||||
raised exception is exposed for matching, as in this PEP; also,
|
||||
arbitrary expressions (possibly involving @@) can be evaluated for
|
||||
exception matching.
|
||||
|
||||
Exceptions in C# contain a read-only 'InnerException' property that
|
||||
may point to another exception. This property is not set by the VM
|
||||
automatically. Instead, the constructors of exception objects all
|
||||
accept an optional 'innerException' argument to explicitly set this
|
||||
property. The C# documentation says "an exception that is thrown as
|
||||
a direct result of a previous exception should include a reference
|
||||
to the previous exception in the InnerException property."
|
||||
|
||||
The reason all three of these attributes are presented together in
|
||||
one proposal is that the '__traceback__' attribute provides
|
||||
convenient access to the traceback on chained exceptions.
|
||||
|
||||
|
||||
Exception Chaining
|
||||
Implicit Exception Chaining
|
||||
|
||||
Here is an example to illustrate the 'context' attribute.
|
||||
Here is an example to illustrate the '__context__' attribute.
|
||||
|
||||
def compute(a, b):
|
||||
try:
|
||||
|
@ -112,11 +146,11 @@ Exception Chaining
|
|||
|
||||
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
|
||||
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:
|
||||
mixture of 'finally' and 'except' clauses:
|
||||
|
||||
def main(filename):
|
||||
file = open(filename) # oops, forgot the 'w'
|
||||
|
@ -142,19 +176,18 @@ Exception Chaining
|
|||
|
||||
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 misspelling of 'clos', whose __context__ points to a NameError
|
||||
due to the misspelling of 'ex', whose __context__ points to an
|
||||
IOError due to the file being read-only, whose __context__ points to
|
||||
a ZeroDivisionError, whose __context__ attribute is 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.
|
||||
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.
|
||||
|
@ -164,9 +197,33 @@ Exception Chaining
|
|||
statement, the thread's exception context is set to None.
|
||||
|
||||
|
||||
Explicit Exception Chaining
|
||||
|
||||
The '__cause__' attribute on exception objects is always initialized
|
||||
to None. It is set by calling the 'setcause' method, a new method
|
||||
defined on the base Exception class. For convenience, this method
|
||||
returns the exception itself.
|
||||
|
||||
In the following example, a database provides implementations for a
|
||||
few different kinds of storage, with file storage as one kind. The
|
||||
database designer wants errors to propagate as DatabaseError objects
|
||||
so that the client doesn't have to be aware of the storage-specific
|
||||
details, but doesn't want to lose the underlying error information.
|
||||
|
||||
class DatabaseError(StandardError):
|
||||
pass
|
||||
|
||||
class FileDatabase(Database):
|
||||
def __init__(self, filename):
|
||||
try:
|
||||
self.file = open(filename)
|
||||
except IOError, exc:
|
||||
raise DatabaseError('failed to open').setcause(exc)
|
||||
|
||||
|
||||
Traceback Attribute
|
||||
|
||||
The following example illustrates the 'traceback' attribute.
|
||||
The following example illustrates the '__traceback__' attribute.
|
||||
|
||||
def do_logged(file, work):
|
||||
try:
|
||||
|
@ -181,7 +238,7 @@ Traceback Attribute
|
|||
...
|
||||
type = exc.__class__
|
||||
message = str(exc)
|
||||
lines = format_tb(exc.traceback)
|
||||
lines = format_tb(exc.__traceback__)
|
||||
file.write(... type ... message ... lines ...)
|
||||
...
|
||||
|
||||
|
@ -189,63 +246,84 @@ Traceback Attribute
|
|||
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.
|
||||
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.
|
||||
1. Whenever an exception is caught, if the exception instance does
|
||||
not already have a '__traceback__' attribute, the interpreter
|
||||
sets it to the newly caught 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:
|
||||
exceptions. The chain of exceptions is traversed by following the
|
||||
'__cause__' and '__context__' attributes, with '__cause__' taking
|
||||
priority. In keeping with the chronological order of tracebacks,
|
||||
the most recently raised exception is displayed last; that is, 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 one of the lines:
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
or
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
between tracebacks, depending whether they are linked by __cause__
|
||||
or __context__ respectively. Here is a sketch of the procedure:
|
||||
|
||||
def print_chain(exc):
|
||||
chain = []
|
||||
link = None
|
||||
while exc:
|
||||
chain.append((exc, link))
|
||||
if exc.__cause__:
|
||||
exc, link = exc.__cause__, 'The above exception...'
|
||||
else:
|
||||
exc, link = exc.__context__, 'During handling...'
|
||||
for exc, link in reversed(chain):
|
||||
print_exc(exc)
|
||||
print '\n' + link + '\n'
|
||||
|
||||
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
|
||||
optional 'chain' 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.
|
||||
The 'cgitb' module should also 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
|
||||
will not set the '__context__' attribute on exceptions. The BDFL has
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
Chained exceptions expose the type of the most recent exception, so
|
||||
they will still 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.
|
||||
The proposed changes should not break any code except for code that
|
||||
currently sets or depends on the values of attributes named
|
||||
'__context__', '__cause__', or '__traceback__' on exception
|
||||
instances. As of 2005-05-12, the Python standard library contains
|
||||
no mention of such attributes.
|
||||
|
||||
|
||||
Open Issues
|
||||
|
@ -257,13 +335,14 @@ Open Issues
|
|||
establishing conventions for other informational attributes on
|
||||
exceptions.
|
||||
|
||||
It is not clear whether the 'context' feature proposed here would
|
||||
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:
|
||||
Addressing this problem is out of the scope of this PEP; it is not a
|
||||
new problem, as demonstrated by the following example:
|
||||
|
||||
>>> def gen():
|
||||
... try:
|
||||
|
@ -279,24 +358,25 @@ Open Issues
|
|||
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
|
||||
- If PEP 340 or PEP 343 is accepted, replace the three (type, value,
|
||||
traceback) arguments to __exit__ with a single exception argument.
|
||||
|
||||
- Deprecate 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
|
||||
- Deprecate 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
|
||||
- Deprecate 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
|
||||
- Upgrade cgitb.html() to accept a single value as its first
|
||||
argument as an alternative to a (type, value, traceback) tuple.
|
||||
|
||||
|
||||
|
@ -304,17 +384,17 @@ Possible Future Incompatible Changes
|
|||
|
||||
These changes might be worth considering for Python 3000.
|
||||
|
||||
- Removing sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
- Remove sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
sys.exc_info().
|
||||
|
||||
- Removing sys.last_type, sys.last_value, and sys.last_traceback.
|
||||
- Remove sys.last_type, sys.last_value, and sys.last_traceback.
|
||||
|
||||
- Replacing the three-argument sys.excepthook with a one-argument
|
||||
- Replace 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.
|
||||
- Remove the three-argument form of the 'raise' statement.
|
||||
|
||||
- Upgrading traceback.print_exception to accept an 'exception'
|
||||
- Upgrade traceback.print_exception to accept an 'exception'
|
||||
argument instead of the type, value, and traceback arguments.
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue