Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 3134 – Exception Chaining and Embedded Tracebacks

Author:
Ka-Ping Yee
Status:
Final
Type:
Standards Track
Created:
12-May-2005
Python-Version:
3.0
Post-History:


Table of Contents

Numbering Note

This PEP started its life asPEP 344. Since it is now targeted for Python3000, it has been moved into the 3xxx space.

Abstract

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. A newraise...from statement sets the__cause__ attribute.

Motivation

During the handling of one exception (exception A), it is possible that anotherexception (exception B) may occur. In today’s Python (version 2.4), if thishappens, exception B is propagated outward and exception A is lost. In orderto 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-raisean exception, either to provide extra information or to translate an exceptionto another type. The__cause__ attribute provides an explicit way torecord the direct cause of an exception.

In today’s Python implementation, exceptions are composed of three parts: thetype, the value, and the traceback. Thesys module, exposes the currentexception in three parallel variables,exc_type,exc_value, andexc_traceback, thesys.exc_info() function returns a tuple of thesethree parts, and theraise statement has a three-argument form acceptingthese three parts. Manipulating exceptions often requires passing these threethings in parallel, which can be tedious and error-prone. Additionally, theexcept statement can only provide access to the value, not the traceback.Adding the__traceback__ attribute to exception values makes all theexception information accessible from a single place.

History

Raymond Hettinger[1] raised the issue of masked exceptions on Python-Dev inJanuary 2003 and proposed aPyErr_FormatAppend() function that C modulescould use to augment the currently active exception with more information.Brett Cannon[2] brought up chained exceptions again in June 2003, promptinga long discussion.

Greg Ewing[3] identified the case of an exception occurring in afinallyblock during unwinding triggered by an original exception, as distinct fromthe case of an exception occurring in anexcept block that is handling theoriginal exception.

Greg Ewing[4] and Guido van Rossum[5], and probably others, havepreviously mentioned adding a traceback attribute to Exception instances.This is noted inPEP 3000.

This PEP was motivated by yet another recent Python-Dev reposting of the sameideas[6][7].

Rationale

The Python-Dev discussions revealed interest in exception chaining for twoquite different purposes. To handle the unexpected raising of a secondaryexception, the exception must be retained implicitly. To support intentionaltranslation of an exception, there must be a way to chain exceptionsexplicitly. This PEP addresses both.

Several attribute names for chained exceptions have been suggested onPython-Dev[2], includingcause,antecedent,reason,original,chain,chainedexc,exc_chain,excprev,previous, andprecursor. For an explicitly chained exception, this PEP suggests__cause__ because of its specific meaning. For an implicitly chainedexception, this PEP proposes the name__context__ because the intendedmeaning is more specific than temporal precedence but less specific thancausation: an exception occurs in the context of handling another exception.

This PEP suggests names with leading and trailing double-underscores for thesethree attributes because they are set by the Python VM. Only in very specialcases should they be set by normal assignment.

This PEP handles exceptions that occur duringexcept blocks andfinallyblocks in the same way. Reading the traceback makes it clear where theexceptions occurred, so additional mechanisms for distinguishing the two caseswould only add unnecessary complexity.

This PEP proposes that the outermost exception object (the one exposed formatching byexcept clauses) be the most recently raised exception forcompatibility with current behaviour.

This PEP proposes that tracebacks display the outermost exception last, becausethis would be consistent with the chronological order of tracebacks (fromoldest to most recent frame) and because the actual thrown exception is easierto find on the last line.

To keep things simpler, the C API calls for setting an exception will notautomatically set the exception’s__context__. Guido van Rossum hasexpressed concerns with making such changes[8].

As for other languages, Java and Ruby both discard the original exception whenanother exception occurs in acatch/rescue orfinally/ensureclause. Perl 5 lacks built-in structured exception handling. For Perl 6, RFCnumber 88[9] proposes an exception mechanism that implicitly retains chainedexceptions in an array named@@. In that RFC, the most recently raisedexception 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-onlyInnerException property that may pointto another exception. Its documentation[10] says that “When an exception Xis thrown as a direct result of a previous exception Y, theInnerExceptionproperty of X should contain a reference to Y.” This property is not set bythe VM automatically; rather, all exception constructors take an optionalinnerException argument to set it explicitly. The__cause__ attributefulfills the same purpose asInnerException, but this PEP proposes a newform ofraise rather than extending the constructors of all exceptions. C#also provides aGetBaseException method that jumps directly to the end oftheInnerException chain; this PEP proposes no analog.

The reason all three of these attributes are presented together in one proposalis that the__traceback__ attribute provides convenient access to thetraceback on chained exceptions.

Implicit Exception Chaining

Here is an example to illustrate the__context__ attribute:

defcompute(a,b):try:a/bexceptException,exc:log(exc)deflog(exc):file=open('logfile.txt')# oops, forgot the 'w'print>>file,excfile.close()

Callingcompute(0,0) causes aZeroDivisionError. Thecompute()function catches this exception and callslog(exc), but thelog()function also raises an exception when it tries to write to a file that wasn’topened for writing.

In today’s Python, the caller ofcompute() gets thrown anIOError. TheZeroDivisionError is lost. With the proposed change, the instance ofIOError has an additional__context__ attribute that retains theZeroDivisionError.

The following more elaborate example demonstrates the handling of a mixture offinally andexcept clauses:

defmain(filename):file=open(filename)# oops, forgot the 'w'try:try:compute()exceptException,exc:log(file,exc)finally:file.clos()# oops, misspelled 'close'defcompute():1/0deflog(file,exc):try:print>>file,exc# oops, file is not writableexcept:display(exc)defdisplay(exc):printex# oops, misspelled 'exc'

Callingmain() with the name of an existing file will trigger fourexceptions. The ultimate result will be anAttributeError due to themisspelling ofclos, whose__context__ points to aNameError dueto the misspelling ofex, whose__context__ points to anIOErrordue to the file being read-only, whose__context__ points to aZeroDivisionError, whose__context__ attribute isNone.

The proposed semantics are as follows:

  1. Each thread has an exception context initially set toNone.
  2. Whenever an exception is raised, if the exception instance does not alreadyhave a__context__ attribute, the interpreter sets it equal to thethread’s exception context.
  3. Immediately after an exception is raised, the thread’s exception context isset to the exception.
  4. Whenever the interpreter exits anexcept block by reaching the end orexecuting areturn,yield,continue, orbreak statement, thethread’s exception context is set toNone.

Explicit Exception Chaining

The__cause__ attribute on exception objects is always initialized toNone. It is set by a new form of theraise statement:

raiseEXCEPTIONfromCAUSE

which is equivalent to:

exc=EXCEPTIONexc.__cause__=CAUSEraiseexc

In the following example, a database provides implementations for a fewdifferent kinds of storage, with file storage as one kind. The databasedesigner wants errors to propagate asDatabaseError objects so that theclient doesn’t have to be aware of the storage-specific details, but doesn’twant to lose the underlying error information.

classDatabaseError(Exception):passclassFileDatabase(Database):def__init__(self,filename):try:self.file=open(filename)exceptIOError,exc:raiseDatabaseError('failed to open')fromexc

If the call toopen() raises an exception, the problem will be reported asaDatabaseError, with a__cause__ attribute that reveals theIOError as the original cause.

Traceback Attribute

The following example illustrates the__traceback__ attribute.

defdo_logged(file,work):try:work()exceptException,exc:write_exception(file,exc)raiseexcfromtracebackimportformat_tbdefwrite_exception(file,exc):...type=exc.__class__message=str(exc)lines=format_tb(exc.__traceback__)file.write(...type...message...lines...)...

In today’s Python, thedo_logged() function would have to extract thetraceback fromsys.exc_traceback orsys.exc_info()[2] and pass boththe value and the traceback towrite_exception(). With the proposedchange,write_exception() simply gets one argument and obtains theexception using the__traceback__ attribute.

The proposed semantics are as follows:

  1. Whenever an exception is caught, if the exception instance does not alreadyhave a__traceback__ attribute, the interpreter sets it to the newlycaught traceback.

Enhanced Reporting

The default exception handler will be modified to report chained exceptions.The chain of exceptions is traversed by following the__cause__ and__context__ attributes, with__cause__ taking priority. In keepingwith the chronological order of tracebacks, the most recently raised exceptionis displayed last; that is, the display begins with the description of theinnermost exception and backs up the chain to the outermost exception. Thetracebacks are formatted as usual, with one of the lines:

Theaboveexceptionwasthedirectcauseofthefollowingexception:

or

Duringhandlingoftheaboveexception,anotherexceptionoccurred:

between tracebacks, depending whether they are linked by__cause__ or__context__ respectively. Here is a sketch of the procedure:

defprint_chain(exc):ifexc.__cause__:print_chain(exc.__cause__)print'\nThe above exception was the direct cause...'elifexc.__context__:print_chain(exc.__context__)print'\nDuring handling of the above exception, ...'print_exc(exc)

In thetraceback module, theformat_exception,print_exception,print_exc, andprint_last functions will be updated to accept anoptionalchain argument,True by default. When this argument isTrue, these functions will format or display the entire chain of exceptionsas just described. When it isFalse, these functions will format ordisplay only the outermost exception.

Thecgitb module should also be updated to display the entire chain ofexceptions.

C API

ThePyErr_Set* calls for setting exceptions will not set the__context__ attribute on exceptions.PyErr_NormalizeException willalways set thetraceback attribute to itstb argument and the__context__ and__cause__ attributes toNone.

A new API function,PyErr_SetContext(context), will help C programmersprovide chained exception information. This function will first normalize thecurrent exception so it is an instance, then set its__context__ attribute.A similar API function,PyErr_SetCause(cause), will set the__cause__attribute.

Compatibility

Chained exceptions expose the type of the most recent exception, so they willstill match the sameexcept clauses as they do now.

The proposed changes should not break any code unless it sets or usesattributes named__context__,__cause__, or__traceback__ onexception instances. As of 2005-05-12, the Python standard library contains nomention of such attributes.

Open Issue: Extra Information

Walter Dörwald[11] expressed a desire to attach extra information to anexception during its upward propagation without changing its type. This couldbe a useful feature, but it is not addressed by this PEP. It could conceivablybe addressed by a separate PEP establishing conventions for other informationalattributes on exceptions.

Open Issue: Suppressing Context

As written, this PEP makes it impossible to suppress__context__, sincesettingexc.__context__ toNone in anexcept orfinally clausewill only result in it being set again whenexc is raised.

Open Issue: Limiting Exception Types

To improve encapsulation, library implementors may want to wrap allimplementation-level exceptions with an application-level exception. One couldtry to wrap exceptions by writing this:

try:...implementationmayraiseanexception...except:importsysraiseApplicationErrorfromsys.exc_value

or this:

try:...implementationmayraiseanexception...exceptException,exc:raiseApplicationErrorfromexc

but both are somewhat flawed. It would be nice to be able to name the currentexception in a catch-allexcept clause, but that isn’t addressed here.Such a feature would allow something like this:

try:...implementationmayraiseanexception...except*,exc:raiseApplicationErrorfromexc

Open Issue: yield

The exception context is lost when ayield statement is executed; resumingthe frame after theyield does not restore the context. Addressing thisproblem is out of the scope of this PEP; it is not a new problem, asdemonstrated by the following example:

>>>defgen():...try:...1/0...except:...yield3...raise...>>>g=gen()>>>g.next()3>>>g.next()TypeError:exceptionsmustbeclasses,instances,orstrings(deprecated),notNoneType

Open Issue: Garbage Collection

The strongest objection to this proposal has been that it creates cyclesbetween exceptions and stack frames[12]. Collection of cyclic garbage (andtherefore resource release) can be greatly delayed.

>>>try:>>>1/0>>>exceptException,err:>>>pass

will introduce a cycle from err -> traceback -> stack frame -> err, keeping alllocals in the same scope alive until the next GC happens.

Today, these locals would go out of scope. There is lots of code which assumesthat “local” resources – particularly open files – will be closed quickly.If closure has to wait for the next GC, a program (which runs fine today) mayrun out of file handles.

Making the__traceback__ attribute a weak reference would avoid theproblems with cyclic garbage. Unfortunately, it would make saving theException for later (asunittest does) more awkward, and it would notallow as much cleanup of thesys module.

A possible alternate solution, suggested by Adam Olsen, would be to insteadturn the reference from the stack frame to theerr variable into a weakreference when the variable goes out of scope[13].

Possible Future Compatible Changes

These changes are consistent with the appearance of exceptions as a singleobject rather than a triple at the interpreter level.

  • IfPEP 340 orPEP 343 is accepted, replace the three (type,value,traceback) arguments to__exit__ with a single exception argument.
  • Deprecatesys.exc_type,sys.exc_value,sys.exc_traceback, andsys.exc_info() in favour of a single member,sys.exception.
  • Deprecatesys.last_type,sys.last_value, andsys.last_tracebackin favour of a single member,sys.last_exception.
  • Deprecate the three-argument form of theraise statement in favour of theone-argument form.
  • Upgradecgitb.html() to accept a single value as its first argument as analternative to a(type,value,traceback) tuple.

Possible Future Incompatible Changes

These changes might be worth considering for Python 3000.

  • Removesys.exc_type,sys.exc_value,sys.exc_traceback, andsys.exc_info().
  • Removesys.last_type,sys.last_value, andsys.last_traceback.
  • Replace the three-argumentsys.excepthook with a one-argument API, andchanging thecgitb module to match.
  • Remove the three-argument form of theraise statement.
  • Upgradetraceback.print_exception to accept anexception argumentinstead of thetype,value, andtraceback arguments.

Implementation

The__traceback__ and__cause__ attributes and the new raise syntaxwere implemented in revision 57783[14].

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”https://mail.python.org/pipermail/python-dev/2003-January/032492.html
[2] (1,2,3)
Brett Cannon explains chained exceptionshttps://mail.python.org/pipermail/python-dev/2003-June/036063.html
[3]
Greg Ewing points out masking caused by exceptions during finallyhttps://mail.python.org/pipermail/python-dev/2003-June/036290.html
[4]
Greg Ewing suggests storing the traceback in the exception objecthttps://mail.python.org/pipermail/python-dev/2003-June/036092.html
[5]
Guido van Rossum mentions exceptions having a traceback attributehttps://mail.python.org/pipermail/python-dev/2005-April/053060.html
[6]
Ka-Ping Yee, “Tidier Exceptions”https://mail.python.org/pipermail/python-dev/2005-May/053671.html
[7]
Ka-Ping Yee, “Chained Exceptions”https://mail.python.org/pipermail/python-dev/2005-May/053672.html
[8]
Guido van Rossum discusses automatic chaining inPyErr_Set*https://mail.python.org/pipermail/python-dev/2003-June/036180.html
[9]
Tony Olensky, “Omnibus Structured Exception/Error Handling Mechanism”http://dev.perl.org/perl6/rfc/88.html
[10]
MSDN .NET Framework Library, “Exception.InnerException Property”http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemexceptionclassinnerexceptiontopic.asp
[11]
Walter Dörwald suggests wrapping exceptions to add detailshttps://mail.python.org/pipermail/python-dev/2003-June/036148.html
[12]
Guido van Rossum restates the objection to cyclic trashhttps://mail.python.org/pipermail/python-3000/2007-January/005322.html
[13]
Adam Olsen suggests using a weakref from stack frame to exceptionhttps://mail.python.org/pipermail/python-3000/2007-January/005363.html
[14]
Patch to implement the bulk of the PEPhttp://svn.python.org/view/python/branches/py3k/Include/?rev=57783&view=rev

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-3134.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2025 Movatter.jp