Re: sys.exc_type etc.

Tim Peters (tim@ksr.com)
Thu, 05 Mar 92 21:28:01 EST

> | [tim suggests the following as a way to introduce optional
> | exception type-&-value-holding names w/o busting current code]
> | try optional_name_or_two-tuple:
> | ...
> I like this idea of having optional exc_{type,val} variables.

Me too, Pete, but I don't really like this syntax. Guido hasn't said
anything but I suspect he thinks its inelegant; I'd agree (sticking the
names on the 'try' interferes with reading the non-exceptional path).

> | try:
> | print kidding
> | except NameError:
> | try: 1/0
> | except:
> | print 'sys.exc_type should certainly be a zero-divide here'
> | # but what should sys.exc_type be here?
> | [tim sez NameError]
> | ...
> |
> | elist = (None, NameError)
> |
> | def absurd(i):
> | try:
> | try:
> | 1/0
> | except DivideByZeroError: # meant ZeroDivisionError
> | print 'caught an exception that doesn\'t exist!'
> | except elist[i], val:
> | print 'caught the', elist[i], 'error on', val
> |
> | absurd(2)
> | [dies with an index error, but a name error and a divide-by-zero
> | error are also unhandled at this point]

> These two examples suggest to me that it is not easy to define exactly
> what the smallest enclosing structure is.

I don't think the examples were related: the first was talking about
what to do when the user handles an exception, the second about what to
do when the user fails to handle the exception. So they're very
different at the outset.

"Smallest enclosing structure" was an unfortunate phrasing in any case;
it happened to be appropriate for the specific example, but the intended
behavior (which I believe is the same as yours) is really more dynamic
than lexical. It's hard to be precise here unless/until there's a more-
or-less formal execution model for Python; the intended behavior would
(I think) be easy to define as a modification to that.

Informally, I would like the value of sys.exc_* at a given program point
P to be defined by the following (which, in the absence of an execution
model, is phrased in a clumsy "bottom up" way):

1. Is P in an 'except' or 'finally' block?
If yes, then "smallest enclosing" is well-defined (in the obvious
(lexical) way!), and I want sys.exc_* to reflect the exception (if
any) that aborted the associated 'try' block.

Else go to step 2.

Subtlety: the "(if any)" was for the benefit of 'finally' blocks,
which may be entered even if their associated 'try' block did not
abort. In that case I expect sys.exc_* to be None.

2. [the dynamic component]
Was I called by anyone?
If 'yes', go back to step 1 after setting P to the call site.
Else (at top level) sys.exc_* should be None.

If we had a detailed account of how Python manages its internal
exception stack, I suspect this might all (except for the treatment of
'finally' blocks) boil down to that 'sys.exc_*' should mirror whatever
happens to be at the top of the exception stack from moment to moment.

Another way to say it: Suppose we had the optional

try exc_type, exc_val:

syntax. Ignoring the bits about crawling up the call stack, I would
like sys.exc_* to return exactly what exc_type/exc_val would return at
every point, provided that I used a uniquely-named exc_type & exc_val
for each distinct 'try' block. It's just a way to get at part of the
same functionality without introducing any new syntax.

> Why do you consider the current behaviour (raising IndexError)
> correct, whereas in the first example you say NameError should be the
> correct exception returned.

Because they're the right things to do <grin>. Here I think you're
comparing what sys.exc_* should be *bound to* when the user is
explicitly handling an exception (1st example) to what Python itself
should *do* when the user fails to handle the exception (2nd example) --
I just don't see them as being related.

Note that I would also consider other behaviors to be "correct" in the
2nd example. When faced with multiple unhandled exceptions, Python
currently appears to report only the most recent (in time); reporting
all or reporting some or reporting only the least recent ... etc are all
OK by me, just so long as *something* gets reported. I suspect the
current behavior is more an accident of implementation than the result
of deliberate design, though, so thought it might be interesting to
consider other behaviors.

> I do see your points, but having exceptions set according to where
> the code is (e.g in an exception block or a try block) may make the code
> difficult to understand,

Sorry, I need examples here; couldn't follow your thought.

> and the compiler could approach the complexity of Perl ;-)

God forbid! My hope/belief is that I'm trying to find a way to get at
something Python already knows anyway (and indeed *must* know else it
couldn't itself keep the exceptions straight ...).

> | ... I believe this *implies* that sys.exc_type and sys.exc_value
> | would always be None (or unbound, or something else equally
> | vacuous) [when printed from a top-level prompt]
>
> Whoops :-)

Na, Guido said that's already the way Python acts -- we're just trying
to give him an elaborate theoretical justification for keeping it that
way <grin>.

skating-on-exceptional-ice-ly y'rs - tim

Tim Peters Kendall Square Research Corp
tim@ksr.com, ksr!tim@uunet.uu.net