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