Re: sys.exc_type etc.

Tim Peters (tim@ksr.com)
Wed, 04 Mar 92 00:29:39 EST

> ...
> I played with the thought of changing the syntax so the handler could
> specify a variable in which to receive the exception type (just like
> it can already do for the exception's "parameter" or "associated
> value"), but quickly realized that the feature is most needed for
> default "except:" clauses, where adding new syntax was impossible to
> do to in an elegant, yet backward-compatible way!

Two things here:

The current syntax is a bit error-prone already. E.g., if I run this:

try:
print kidding
except NameError:
print 'No kidding!'

it (unsurprisingly) prints 'No kidding!'. But if I put another chunk of
code before it like so:

try:
1/0
except ZeroDivisionError, NameError:
print 'caught the division'

try:
print kidding
except NameError:
print 'No kidding!'

I get:

caught the division
Unhandled exception: NameError: kidding
Stack backtrace (innermost last):
File "exc1.py", line 7
print kidding

The difference between the incorrect (catches only divide-by-zero, & if
that happens binds the local name 'NameError' to the argument of the
ZeroDivisionError exception):

except ZeroDivisionError, NameError:

above and the correct (does what most people thought the one above would
do <grin>):

except (ZeroDivisionError, NameError):

is a bit subtle, and hard to "see" when you make the mistake.

Second, if you were to change the syntax to allow specifying 'type' and
'value' variables, I think the place to do it is not on the 'except'
clauses but on the 'try' statement itself. I.e.,

try optional_name_or_two-tuple:

where optional_name_or_two-tuple is bound to an appropriate (type,value)
tuple if an exception occurred, and, for the benefit of 'finally'
blocks (& maybe too for following code ...), is bound to (None,None) if
no exception occurred. E.g.,

try exctype, excval:
blah blah blah
except NameError:
# exctype is 'NameError' here, excval is the detail
except (TypeError, EOFError), msg:
# exctype is whichever of the two occurred, &
# for compatibility w/ the current scheme both msg
# & excval hold the detail
except:
# exctype has the type, excval the detail
finally: # note that the boolean-vrbl trick isn't needed
if exctype:
print 'bad user! you screwed up in a way'
print 'i couldn\'t predict!'

Having exctype available in the 'finally' block might also be handy if
clean-up actions depend on the nature of the exception.

> ... Tim thinks he wants them [sys.exc_type etc] set each time an
> exception occurs, but there's a problem with that.

"Each time", yes, but I didn't say exactly *when* "each time" <grin>.
Agree that setting them at the instant the exception occurs would lead
to problems.

> The solution is to set sys.exc_type at the moment the exception
> handler is entered.

Not sure that's quite it, either, but am sure it's half of it.

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?

I believe what you sketched would leave me thinking I was staring at a
divide-by-zero error at the position of the comment. But I still want
to see a NameError at that point -- the divide by zero is history at
that point. In nested structures, I always want to see the stuff that's
relevant to the smallest *enclosing* structure; & at the position of the
comment, that's the 'try' block that suffered the name error. The point
to that may be clearer by replacing the block at the end; I don't want
to have to know what somefunc does:

except NameError:
somefunc()
# what should sys.exc_type be here?

I believe Peter Ho's comment leads to the same end here, hence there's
universal convergence <grin>. One funny side-effect: I believe this
*implies* that sys.exc_type and sys.exc_value would always be None (or
unbound, or something else equally vacuous) if printed from a top-level
prompt (the interpreter stack is completely unwound; there is no
exception pending in the "smallest enclosing structure" because there is
no enclosing structure). But maybe that's just another way of saying
that both the sys.exc_... and sys.last_... flavors are useful.

> ... folks, speak now or be silent forever! :-)

Dream on <snort>.

A related question, but on a different tack. The following program
(correctly) dies with an IndexError on the "except elist[i], val:" line:

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)

The details of that silly code are irrelevant. The *point* is that at
the time it dies, it's trying to handle an unhandled NameError exception
from the "except DivideByZeroError" line, while that in turn was trying
to handle the unhandled ZeroDivisionError. It bugs me (a little, & more
in the abstract sense) that the unresolved ZeroDivisionError and
NameError exceptions go unreported. Then again, I'd never write code
with an error in an exception-handler <grin> ...

verbally y'rs - tim

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