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

Ka-Ping Yee python-dev at zesty.ca
Fri May 20 10:24:21 CEST 2005


On Mon, 16 May 2005, Guido van Rossum wrote:
> Here's a bunch of commentary:

Thanks.  Sorry it's taken me a couple of days to get back to this.
I think i'm caught up on the mail now.

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

You're right.  I missed that.

In my initial research i was only looking for implicit chaining, and
the only support for that i could find was the proposed @@ in Perl 6.
Later i went back and added explicit chaining, realizing that this
was what some of the interested parties originally wanted (and that
C# had it too).

> 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():

Got it.

> There's too much text devoted early on to examples.

Okay.  In the next revision of the PEP, i'll rearrange it.

> 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.

That's true.  I agree that the semantics in the PEP (v1.7) are broken.

> 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 don't think it's absolutely necessary, though it doesn't seem to
hurt.  We agree that if the TypeError makes it up to foo's frame,
it should have the ZeroDivisionError as its __context__, right?

If so, do i understand correctly that you want the __context__ to
depend on where the exception is caught as well as where it is raised?

In your thinking, is this mainly a performance or a cleanliness issue?

Basically i was looking for the simplest description that would
guarantee ending up with all the relevant tracebacks reported in
chronological order.  I thought it would be more complicated if we
had to keep modifying the traceback on the way up, but now that
i've re-learned how tracebacks are constructed, it's moot -- we're
already extending the traceback on the way through each frame.

I have a proposal for the implicit chaining semantics that i'll post
in a separate message so it isn't buried in the middle of this one.

> When chaining exceptions, I think it should be an error if the cause
> is not an exception instance (or None).

That's reasonable.

> 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.

I went back and forth on this.  An earlier version of the PEP actually
proposes a 'setcause' method.  I eventually settled on a few reasons
for the "raise ... from" syntax:

    1.  (major) No possibility of method override; no susceptibility
        to manipulation of __dict__ or __getattr__; no possibility of
        another exception happening while trying to set the cause.

    2.  (moderate) There is a clear, distinct idiom for exception
        replacement requiring that the cause and effect must be
        identified together at the point of raising.

    3.  (minor) No method namespace pollution.

    4.  (minor) Less typing, less punctuation.

The main thing is that handling exceptions is a delicate matter, so it's
nice to have guarantees that the things you're doing aren't going to
suddenly raise more exceptions.

> Why insert a blank line between chained tracebacks?

Just to make them easier to read.  The last line of each traceback
is important because it identifies the exception type, and that will
be a lot easier to find if it isn't buried in an uninterrupted stream
of lines.

> 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.

How about if the line says how many exceptions there were?  Like:

    [This is the last of 5 exceptions; see above for the others.]

> Why should the C level APIs not automatically set __context__?  (There
> may be an obvious reason but it doesn't hurt stating it.)

Because:

    (a) you indicated some discomfort with the idea, perhaps because
        it would make the interpreter do unnecessary work;
    (b) no one seems to be asking for it;
    (c) it seems potentially complicated.

However, if we go for the semantics you want, PyErr_Set* wouldn't set
__context__ at the moment of raising anyway.  If __context__ is set
during unwinding, then i expect it would get set on exceptions raised
from C too, since the interpreter wouldn't know the difference.

> 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.

I agree -- i just didn't want to tackle that issue in this PEP.
It could be considered a separate enhancement/bugfix.

> 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).

Yes, exactly.

> 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.

Isn't it a reasonable possibility that, as part of its contract, a
library will want to guarantee that it only raises exceptions of
certain types?

> 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.

If we get there, yes.  But at the moment, i don't believe there's any
way to catch an arbitrary string exception or an exception of a
non-Exception instance other than "except:".

> 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.)

That would be nice, again once we have all exceptions derive from
Exception.  It seems to me we'd have to do these changes in this order:

    (a) ban string exceptions
    (b) require all exceptions to derive from Exception
    (c) ban bare "except:"
    (d) eliminate sys.exc_*

Or do them all at once in Python 3000.  (Well, i guess all that is just
repeating what Brett has talked about putting in his exception PEP.)

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

Okay.

> 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).

Okay.

> 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?)

Dare i suggest... a string subclass with a __traceback__ attribute?

A string subclass (that also subclasses Exception) might be a migration
path to eliminating string exceptions.


-- ?!ng


More information about the Python-Dev mailing list