Python Mystery Theatre -- Episode 1: Exceptions
Chris Reedy
creedy at mitretek.org
Mon Jul 14 13:17:03 EDT 2003
Ok. I'll give this a try. For reference, I fall into the class of users
who have read the docs more than once. (I also have been a college
professor at one point in my career.)
Chris
P.S. I've already read other peoples answers; but, I'll try not to let
that affect mine too much.
Raymond Hettinger wrote:
> ACT I ---------------------------------------
>
>>>>s = list('abc')
>>>>try:
>
> ... result = s['a']
> ... except IndexError, TypeError:
> ... print 'Not found'
> ...
>
> Traceback (most recent call last):
> File "<pyshell#11>", line 2, in -toplevel-
> result = s['a']
> TypeError: list indices must be integers
I didn't have to think about this one. It comes up often enough on
c.l.py and I've been personally bitten by it as well.
The first question that struck me is why the user was trying to use a
string index on a list. Two possible answers: (1) This was included by
Raymond just to trigger the exception. (2) The individual is actually
confused about the differences between lists and dictionaries and was
expecting something like this to happen:
>>> s = list('abc')
>>> s['a']
0
In the latter case, I don't have any blindingly obvious comment except
to review the differences between lists and dictionaries.
The second question (the one I expect Raymond was really getting at) is
why the TypeError was not caught. The answer is that:
except IndexError, TypeError:
is syntactically the same as:
except IndexError, foo:
that is that the variable TypeError is created as a new local variable
which is assigned the exception that was raised, the same as what you
expected to happen when you used foo instead. The fix is:
except (IndexError, TypeError):
or maybe even to do:
except (IndexError, TypeError), foo:
to provide an additional visual clue as to exactly what is happening.
> ACT II --------------------------------------------
>
>>>>class MyMistake(Exception):
>
> ... pass
>
>
>>>>try:
>
> ... raise MyMistake, 'try, try again'
> ... except MyMistake, msg:
> ... print type(msg)
> ...
>
> <type 'instance'>
I learned something on this one. (I had to try this one to confirm my
suspicions.) The user is expecting this to print something like
'MyMistake', or maybe something like:
<class '__main__.MyMistake'>
The problem here is that Exception is an old-style class and type(x)
when x is an instance of an old-style class is always 'instance'. What
the user should do is:
print msg.__class__
> ACT III --------------------------------------------
>
>>>>class Prohibited(Exception):
>
> ... def __init__(self):
> ... print 'This class of should never get initialized'
> ...
>
>>>>raise Prohibited()
>
> This class of should never get initialized
>
> Traceback (most recent call last):
> File "<pyshell#40>", line 1, in -toplevel-
> raise Prohibited()
> Prohibited: <unprintable instance object>
>
>>>>raise Prohibited
>
> This class of should never get initialized
>
> Traceback (most recent call last):
> File "<pyshell#41>", line 1, in -toplevel-
> raise Prohibited
> Prohibited: <unprintable instance object>
This one contains (at least) three issues that I could find.
1. The print statement 'This class should never get initialized',
appears to be an attempt to write an abstract class. Unfortunately, this
is not done properly. One problem here is that the Exception aspect of
prohibited is not initialized. This is what causes the '<unprintable
instance object>' behavior when instances of Prohibited are printed.
2. (After some experimenting on my part.) The phrase '<unprintable
instance object>' is produced when the __str__ method applied to an
exception when printing a traceback raises an exception. (I would assume
that this is required to avoid problems with recursive exceptions.)
3. (I already knew this one.) The fact that 'raise Prohibited()' and
'raise Prohibited' exhibit the same behavior is the result of the fact
that raising an instance of a class will raise that instance, raising a
class will cause an instance of that class to be constructed and then
raised.
> ACT IV -----------------------------------------------
>
>>>>module = 'Root'
>>>>try:
>
> ... raise module + 'Error'
> ... except 'LeafError':
> ... print 'Need leaves'
> ... except 'RootError':
> ... print 'Need soil'
> ... except:
> ... print 'Not sure what is needed'
> ...
>
> Not sure what is needed
This one is easy. (I knew this already from my second reading of the
documentation.) String exceptions are compared by object identity, that
is when 'RootError' is theException, rather than when 'RootError' == the
Exception, which is almost surely what the user was expecting. In
general when the string is constructed, like in this example, it becomes
very difficult no way to catch the exception.
If you want to throw string exceptions which are subsequently caught (I
can't think of a reason for doing this as opposed to defining a subclass
of Exception) you can try:
foo = 'My Error'
try:
...
raise foo
except foo:
print 'Foo caught'
which guarantees that the strings are identical.
Aside: (I wouldn't want to raise this to anyone who didn't already
understand the above.) This example also reveals that funny aspect of
the Python about the interpreter automatically interning strings that
look like variable names. Thus, in the example, the string 'RootError'
had to be constructed. If it was a literal, the example would have
behaved as "expected".
> ACT V -----------------------------------------------
>
>>>>try:
>
> ... raise KeyError('Cannot find key')
> ... except LookupError, msg:
> ... print 'Lookup:', msg
> ... except OverflowError, msg:
> ... print 'Overflow:', msg
> ... except KeyError, msg:
> ... print 'Key:', msg
>
>
> Lookup: 'Cannot find key'
(I had to confirm my guess on this one.) KeyError is a sub-class of
LookupError. So the except LookupError clause caught the exception
before the except KeyError clause was even checked. If you want to catch
both KeyError and LookupError in the same set of exceptions (which in my
mind is a questionable proposition), you would do:
except KeyError, msg:
...
except LookupError, msg:
...
Since the except clauses are processed serially, this would cause the
check for KeyError to occur before the one for LookupError.
More information about the Python-list
mailing list