diff --git a/pep-0344.txt b/pep-0344.txt new file mode 100644 index 000000000..08fe0b915 --- /dev/null +++ b/pep-0344.txt @@ -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.