[Python-Dev] PEP 344: Exception Chaining and Embedded Tracebacks

Guido van Rossum gvanrossum at gmail.com
Tue May 17 01:11:36 CEST 2005


[Ka-Ping Yee]
> This PEP is a concrete proposal for exception chaining, to follow
> up on its mention here on Python-Dev last week as well as earlier
> discussions in the past year or two.
> 
>     http://www.python.org/peps/pep-0344.html

Here's a bunch of commentary:

You're not giving enough credit to Java, which has the "cause" part
nailed IMO.

I like the motivation and rationale, but I think the spec is weak and
would benefit from a better understanding of how exception handling
currently works.  In particular, please read and understand the
comment in ceval.c starting with this line:

    /* Implementation notes for set_exc_info() and reset_exc_info():

There's too much text devoted early on to examples.  I think these
should come at the end; in particular, hiding the proposed semantics
at the very end of a section that otherwise contains only illustrative
examples is a bad idea (especially since the examples are easy enough
to guess, if you've read the rationale).

I don't think the PEP adequately predicts what should happen in this
example:

    def foo():
	try:
	    1/0  # raises ZeroDivisionError
	except:
	    bar()
	    raise sys.does_not_exist  # raises AttributeError

    def bar():
	try:
	    1+""  # raises TypeError
	except TypeError:
	    pass

Intuitively, the AttributeError should have the ZeroDivisionError as
its __context__, but I think the clearing of the thread's exception
context when the except clause in bar() is left will drop the
exception context.  If you follow the save/restore semantics described
in that ceval.c comment referenced above, you'll get the correct
semantics, I believe.

Also, in that same example, according to your specs, the TypeError
raised by bar() has the ZeroDivisionError raised in foo() as its
context.  Do we really want this?  I still have the feeling that
perhaps the context ought to be attached later, e.g. only when an
exception "passes through" a frame that happens to be handling an
exception already (whether in an except clause or in a finally
clause).

When chaining exceptions, I think it should be an error if the cause
is not an exception instance (or None).  Yes, this will just
substitute a different exception, but I still think it's the right
thing to do -- otherwise code walking the chain of causes must be
constantly aware of this possibility, and since during normal use it
will never happen, that would be a great way to trip it up (perhaps
even to cause a circularity!).

Do we really need both __context__ and __cause__?  Methinks that you
only ever need one: either you explicitly chain a new exception to a
cause, and then the context is probably the same or irrelevant, or you
don't explicitly chain, and then cause is absent.  Since the traceback
printing code is to prefer __cause__ over __context__, why can't we
unify these?  About the only reason I can think of is that with
__cause__ you know it was intentional and with __context__ you know it
wasn't; but when is it important knowing the difference?

Do we really need new syntax to set __cause__?  Java does this without
syntax by having a standard API initCause() (as well as constructors
taking a cause as argument; I understand why you don't want to rely on
that -- neither does Java).  That seems more general because it can be
used outside the context of a raise statement.

Why insert a blank line between chained tracebacks?

In Java, I often find the way chained tracebacks are printed
confusing, because the "deepest" stack frame (where the exception
originally occurred) is no longer at the top of the printout.  I
expect the same confusion to happen for Python, since it prints
everything in the exact opposite order as Java does, so again the
original exception is somewhere in the middle.  I don't think I want
to fix this by printing the outermost exception first and the chained
exception later (which would keep the stack frames in their proper
order but emphasizes the low-level exception rather than the one that
matches the except clause that would have caught it at the outermost
level), but I might want to add an extra line at the very end (and
perhaps at each chaining point) warning the user that the exception
has a chained counterpart that was printed earlier.

Why should the C level APIs not automatically set __context__?  (There
may be an obvious reason but it doesn't hurt stating it.)  You're
unclear on how the C code should be modified to ensure that the proper
calls to PyErr_SetContext() are made.

I was surprised to learn that yield clears the exception state; I
wonder if this isn't a bug in the generator implementation?  IMO
better semantics would be for the exception state to survive across
yield.

You should at least mention what should happen to string exceptions,
even if (as I presume) the only sane approach is not to support this
for string exceptions (a string exception may be the end of the chain,
but it cannot have a chained exception itself).

I don't like the example (in "Open Issues") of applications wrapping
arbitrary exceptions in ApplicationError.  I consider this bad style,
even if the chaining makes it not quite as bad as it used to be.

I don't see the need for "except *, exc" -- assuming all exceptions
derive from a single base class, we can just write the name of that
base class.

I don't like having sys.exception; instead, the only way to access the
"current" exception ought to be to use an except clause with a
variable.  (sys.last_exception is fine.)

I like the idea of taking all APIs that currently require a (type,
value, traceback) triple to *also* accept a single exception instance.

You should probably reference the proposal (pending a PEP; I think
Brett is working on it?) that all exceptions should eventually derive
from a common base class (a la Throwable in Java).

I hope that this can be accepted together with the son-of-PEP-343 (PEP
343 plus generator exception injection and finalization) so __exit__
can take a single exception argument from the start.  (But what should
it receive if a string exception is being caught?  A triple perhaps?)

-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-Dev mailing list