Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Python Mystery Theatre -- Episode 1: Exceptions

1 view
Skip to first unread message

Raymond Hettinger

unread,
Jul 12, 2003, 2:56:52 AM7/12/03
to
For your amusement and edification, I'm working on a
series of Python puzzles designed to highlight areas of
the language known only to those who have read the
docs more than once.

Each of the following sections contains a code snippet
that someone, somewhere might find a little mysterious.

Your goal is to sleuth through the code, identify what
was expected versus what really happened, and to
explain it briefly so that others will find it blindingly
obvious.

The mysteries are not difficult, but I would suprised
if most readers didn't learn something new along the way.

Of course, if you are a potential bot, then these mysteries
will be very difficult because all you will see is the code
operating as designed, implemented, documented and tested.

When you post your results, I would be interested in
knowing whether you already knew what was going
on or whether you had to resort to:

* reading other posts
* googling
* experimenting
* or worse, re-reading the docs


Raymond Hettinger


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

ACT II --------------------------------------------
>>> class MyMistake(Exception):
... pass

>>> try:
... raise MyMistake, 'try, try again'
... except MyMistake, msg:
... print type(msg)
...

<type 'instance'>

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>

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

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'


Ben Finney

unread,
Jul 12, 2003, 2:44:00 AM7/12/03
to
On Sat, 12 Jul 2003 06:56:52 GMT, Raymond Hettinger wrote:
> For your amusement and edification, I'm working on a series of Python
> puzzles designed to highlight areas of the language known only to
> those who have read the docs more than once.

Excellent stuff! Any plans to put these online, perhaps with collation
of the responses? How many episodes are currently planned?

I imagine that some of the "blindingly obvious" explanations would be
welcome in the docs :-)

--
\ "I wish a robot would get elected president. That way, when he |
`\ came to town, we could all take a shot at him and not feel too |
_o__) bad." -- Jack Handey |
http://bignose.squidly.org/ 9CFE12B0 791A4267 887F520C B7AC2E51 BD41714B

Raymond Hettinger

unread,
Jul 12, 2003, 3:33:51 AM7/12/03
to
> Your goal is to sleuth through the code, identify what
> was expected versus what really happened, and to
> explain it briefly so that others will find it blindingly
> obvious.

P.S. There's extra credit if you can also devine why Python
was designed/implemented with the demonstrated behaviors.


Raymond Hettinger


Erik Max Francis

unread,
Jul 12, 2003, 4:02:16 AM7/12/03
to
Raymond Hettinger wrote:

(I've pasted in code I needed to use to illustrate or understand the
problems.)

> 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

This one's easy. You didn't try to catch IndexErrors and TypeErrors,
you tried to catch an IndexError and, if caught, assign the exception
instance to TypeError. The except clause takes an exception class or
tuple of exception classes, and then an optional instance variable name,
and then some other optional things. So:

...
except (IndexError, TypeError):
...

will fix this.

> ACT II --------------------------------------------
> >>> class MyMistake(Exception):
> ... pass
>
> >>> try:
> ... raise MyMistake, 'try, try again'
> ... except MyMistake, msg:
> ... print type(msg)
> ...
>
> <type 'instance'>

I'm not sure what mystery you're trying to get at here. This is what
Python prints for all instances:

>>> class C:
... pass
...
>>> c = C()
>>> type(c)
<type 'instance'>

An exception is an instance of an exception class, so it looks like an
instance like any other. If you wanted the name of the class, then use
__class__:

>>> c.__class__
<class __main__.C at 0x814f9e4>
>>> c.__class__.__name__
'C'

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

Is this some IDLE-specific thing? I don't see this at all:

>>> class Prohibited(Exception):
... def __init__(self):

... print 'This class should have never gotten initialized'
...
>>> raise Prohibited()
This class should have never gotten initialized


Traceback (most recent call last):

File "<stdin>", line 1, in ?
__main__.Prohibited>>>
>>> raise Prohibited
This class should have never gotten initialized


Traceback (most recent call last):

File "<stdin>", line 1, in ?
__main__.Prohibited>>>

There is indeed no newline between the printed names of the class and
the following prompt, this is not a pasting error, which strongly
suggests to me that it's what you're trying to get at but is exhibiting
itself in a different way in the interactive interpreter vs. IDLE.
Undoubtedly it happens because Prohibited overrides Exception, and
Prohibited needs an __init__ method, but that method does not call
Exception.__init__. At the very least, fixing this for me makes the
output a little more sane:

>>> class Proscribed(Exception):
... def __init__(self):
... Exception.__init__(self)
... print "That information is proscribed. Enter your DNA#."
...
>>> raise Proscribed()
That information is proscribed. Enter your DNA#.


Traceback (most recent call last):

File "<stdin>", line 1, in ?
__main__.Proscribed
>>> raise Proscribed
That information is proscribed. Enter your DNA#.


Traceback (most recent call last):

File "<stdin>", line 1, in ?
__main__.Proscribed

> 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

You used string exceptions and so deserve punishment :-). In this case,
string exceptions in except clauses are tested by identity, not
equality, so building them with concatenation is unlikely to create a
string with the same ID:

>>> s = "dataspace retrieval"
>>> t = "dataspace " + "retrieval"
>>> s is t
0
>>> s == t
1

Equality and identity aren't the same thing, and this is one of the rare
cases in Python where identity really matters (usually, it's merely an
optimization implementation detail). Short answer: Don't use string
exceptions. Long answer: Seriously, don't use string exceptions.

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

This means that KeyError is a subclass of LookupError (something I
wouldn't have known off the top of my head but which was easy to
verify):

>>> issubclass(KeyError, LookupError)
1

Except clauses go in order and test to see whether the exception object
is an instance of the specified class or any of its subclasses. Since
KeyError is a subclass of LookupError, all KeyErrors are LookupErrors
too, and that's the except clause that gets executed. If you actually
_did_ want to distinguish between KeyErrors and LookupErrors, you can
put the KeyError clause first:

...
except KeyError, e:
...
except LookupError, e:
...
...

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
__ San Jose, CA, USA && 37 20 N 121 53 W && &tSftDotIotE
/ \ God said: "Let Newton be"; and all was light.
\__/ Alexander Pope

Peter Hansen

unread,
Jul 12, 2003, 5:43:36 AM7/12/03
to
Erik Max Francis wrote:
>
> > ACT II --------------------------------------------
> > >>> class MyMistake(Exception):
> > ... pass
> >
> > >>> try:
> > ... raise MyMistake, 'try, try again'
> > ... except MyMistake, msg:
> > ... print type(msg)
> > ...
> >
> > <type 'instance'>
>
> I'm not sure what mystery you're trying to get at here. This is what
> Python prints for all instances:
[snip]

He's showing a case where a programmer thought he/she was using
a sort of "parallel form" with the "MyMistake, 'try, try again'"
part and the "MyMistake, msg" part.

The programmer expected print type(msg) to show "<type 'str'>".

This is probably an example of an error promoted by leaving the
redundant "raise Class,args" form of exception-raising in Python,
instead of having a single obvious way: "raise Class(args)" as
would be more Pythonic. ;-)

-Peter

Jack Diederich

unread,
Jul 12, 2003, 5:19:05 AM7/12/03
to
On Sat, Jul 12, 2003 at 06:56:52AM +0000, Raymond Hettinger wrote:

Since you included the answers I was really unsuprised by what happened,
but maybe I'm wrong as to the why (no other posts, docs, or searching read
for my explanations).

> 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

A TypeError was thrown, but writing this as
except (IndexError,), type_error:
explains why it wasn't caught.

> ACT II --------------------------------------------
> >>> class MyMistake(Exception):
> ... pass
>
> >>> try:
> ... raise MyMistake, 'try, try again'
> ... except MyMistake, msg:
> ... print type(msg)
> ...
>
> <type 'instance'>

type() of most any (new style?) object will print this

> 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 is new to me, FWIW I write two kinds of exceptions,
one that is just defined as MyGuy(Excption):pass and another
where I define both __init__ and __repr__ to print what I want.

> 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

There is a reason string exceptions are deprecated ;)
the string 'RootError' is not a subclass of the string 'RootError'
and thus won't be caught.

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

LookupError is the parent of KeyError and IndexError. I generally
catch the more specific list/dict exceptions depending on what I'm
trying to access.


The fact that I'm very certain about my answers increases the likelyhood
that I'm in fact wrong ;)

-jack

[I orignally had a long thing about the weird exceptions thrown by mmap here,
but decided A: no one cared, B: a patch would be better than bitching. But
really, how can a method with no arguments raise a ValueError?]

Erik Max Francis

unread,
Jul 12, 2003, 7:12:24 AM7/12/03
to
Peter Hansen wrote:

> This is probably an example of an error promoted by leaving the
> redundant "raise Class,args" form of exception-raising in Python,
> instead of having a single obvious way: "raise Class(args)" as
> would be more Pythonic. ;-)

But, as I recall, PEP 317 was outright rejected, so it looks like this
will be with us for a long time.

I personally have never had a problem with the distinction, raise C, x
always seemed fairly clean to me even though really what you mean is
raise C(x).

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
__ San Jose, CA, USA && 37 20 N 121 53 W && &tSftDotIotE

/ \ I want a martini that could be declared a disaster area.
\__/ Capt. Benjamin "Hawkeye" Pierce

Aahz

unread,
Jul 12, 2003, 10:40:34 AM7/12/03
to
In article <3F0FC088...@alcyone.com>,
Erik Max Francis <m...@alcyone.com> wrote:

>Raymond Hettinger wrote:
>>
>> 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>
>
>Is this some IDLE-specific thing?

Nope, the point here is that

raise Prohibited

will always create a Prohibited() instance. (See also Peter's post
about One True Way for exceptions.)
--
Aahz (aa...@pythoncraft.com) <*> http://www.pythoncraft.com/

"Not everything in life has a clue in front of it...." --JMS

Steven Taschuk

unread,
Jul 12, 2003, 3:09:36 PM7/12/03
to
Quoth Erik Max Francis:
[...]

> But, as I recall, PEP 317 was outright rejected, so it looks like this
> will be with us for a long time.

It was indeed rejected, primarily on the grounds that its putative
benefit did not justify the cost of migration. In the end, even I
(the PEP author) agree with that assessment.

I still believe, however, that the implicit instantiation which
Raymond's Acts II and III illustrate is a wart, fully deserves
inclusion in Python Mystery Theatre, and, as a matter of style,
should usually be avoided. Of course, ...

> I personally have never had a problem with the distinction, raise C, x
> always seemed fairly clean to me even though really what you mean is
> raise C(x).

... opinions vary. Guido, for example, was not convinced by the
PEP's arguments that implicit instantiation is a Bad Thing. (Note
that even if he had been, the migration cost would still have sunk
the PEP.)

After being rejected, the PEP grew the section
<http://www.python.org/peps/pep-0317.html#summary-of-discussion>
which briefly discusses these points and others.

--
Steven Taschuk o- @
stas...@telusplanet.net 7O )
" (

John J. Lee

unread,
Jul 12, 2003, 5:59:10 PM7/12/03
to
Oops, still not used to Gnus. My followup to the 'What's new with
Gnosis' thread was supposed to be to this thread.


John

John J. Lee

unread,
Jul 12, 2003, 6:03:34 PM7/12/03
to
aa...@pythoncraft.com (Aahz) writes:

> In article <3F0FC088...@alcyone.com>,
> Erik Max Francis <m...@alcyone.com> wrote:

[...]


> >> Prohibited: <unprintable instance object>
> >
> >Is this some IDLE-specific thing?
>
> Nope, the point here is that
>
> raise Prohibited
>
> will always create a Prohibited() instance. (See also Peter's post
> about One True Way for exceptions.)

Perhaps Erik was wondering, as I was, where that "<unprintable instance
object>" came from. On my machine, 2.3b1 doesn't print that in
response to Raymond's example code. Maybe it's from 2.3b2 (which I'm
downloading ATM), or IDLE, or something else?


John

Erik Max Francis

unread,
Jul 12, 2003, 7:11:23 PM7/12/03
to
"John J. Lee" wrote:

> Perhaps Erik was wondering, as I was, where that "<unprintable
> instance
> object>" came from.

Indeed. I was quite aware of what was happening, just not clear on why
his particular Python session said something that mine didn't.

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
__ San Jose, CA, USA && 37 20 N 121 53 W && &tSftDotIotE

/ \ Now I must follow them!
\__/ Beowulf, King of the Geats

Jason Trowbridge

unread,
Jul 13, 2003, 6:33:17 PM7/13/03
to
Okay, without looking at anybody else's answers, using the docs, or
trying out the examples. This is straight from the head, cold.

Act I
's' is a list, and you can't lookup the resulting indice of the
sequence using the brackets. A s.index( 'a') would be valid.

As for why the exception isn't caught: There is a missing tuple there.
The following would work:
except (IndexError, TypeError):

The way it is currently given would only catch IndexError exceptions.
The exception instance would then be bound to the TypeError name.

Act II
The type of msg is always type instance, because it is a class
instance. The actual class is held under the msg.__class__ attribute.

Act III
I believe that you need to call Exception.__init__(self) in your
constructor. When the string representation of the exception is
printed, it use's Exception.__str__() (maybe Exception.__repr__(). I
forget which one the traceback shows). Since the instance hasn't been
initialized properly, that function can't print out its information.

Act IV
This has to do with string exceptions. I'm not sure how a specific
string exception can be caught. In any case, this is part of the
reason to stay away from string exceptions. Additionally, string
exceptions are scheduled to eventually disappear from the language.

Act V
Without looking at the docs, I'd say that KeyError is derived from the
LookupError class. Exceptions are supposed to be listed most-specific
to least specific, which translates to child classes before parent
classes here.

Hmm, time to look at the answers other people have given, and find how
badly off I am!

_ () () Jason Trowbridge | "There has been a coup. The Mac
( ' .~. Generic Programmer | users are liberating the printers."
\ = o = | --Schlake
---"`-`-'"---+ rat...@nmt.edu |

Michael Hudson

unread,
Jul 14, 2003, 12:58:05 PM7/14/03
to
Erik Max Francis <m...@alcyone.com> writes:

> "John J. Lee" wrote:
>
> > Perhaps Erik was wondering, as I was, where that "<unprintable
> > instance
> > object>" came from.
>
> Indeed. I was quite aware of what was happening, just not clear on why
> his particular Python session said something that mine didn't.

It's probably a traceback.print_exc vs. the builtin traceback printing
stuff thing?

>>> class C(Exception):
... def __init__(self):
... pass
...
>>> raise C


Traceback (most recent call last):

File "<stdin>", line 1, in ?

__main__.C>>> import traceback
>>> traceback.print_last()


Traceback (most recent call last):

File "<stdin>", line 1, in ?

C: <unprintable instance object>

Seems so.

Cheers,
M.

--
Any form of evilness that can be detected without *too* much effort
is worth it... I have no idea what kind of evil we're looking for
here or how to detect is, so I can't answer yes or no.
-- Guido Van Rossum, python-dev

Chris Reedy

unread,
Jul 14, 2003, 1:17:03 PM7/14/03
to
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.

Michael Chermside

unread,
Jul 15, 2003, 8:02:43 AM7/15/03
to
First of all, Raymond THANK YOU. This is lots of fun, and educational too.
In fact, I'm nominating it for my favorite c.l.py posting so far this
year!

So... here's how I did. [** SPOILERS BELOW **]

ACT I:
This one stumped me the longest... I stared and stared and just
couldn't figure it out. But when I came back the next morning
it was obvious.

"except foo, bar" catches things of type foo, and binds bar
to the object caught. To catch multiple types you stick them
in a tuple. (This beats out Java where there's no way I know
of to catch multiple types.) Design wise, I think the syntax
is a little odd, but not worth changing.

ACT II:
Easy... when you write "raise foo, bar" Python executes foo(bar)
and then raises that object. So of course the object, when you
catch it, is an instance.

ACT III:
It took me a while to figure out what you were EXPECTING here,
I guess a more complete sumary would be that "raise foo, bar"
raises the object "foo(bar)", while "raise foo" either raises
the object "foo" or the object "foo()". The rules for which to
do are simply that if foo is of type "type" (ie, it's an old-style
class) then it raises "foo()", otherwise "foo". In my opinion,
this is an unnecessary bit of DWIM (DoWhatIMean), and things
would be much cleaner if you just always said "raise foo" and
it raised the object "foo" -- at worst you'd need an extra set
of parenthesees -- but we recently discussed this in PEP-317 and
in the end, Guido (and others) succeeded in convincing me (and
others) that it's not worth changing, and he mapped out a plan
for how to make the DWIM decision (to raise "foo" or "foo()")
after the change to new-style classes. I do, however, consider
if a minor wart.

Anyhow, it's thanks to the PEP-317 discussions that I understood
this one.

http://mail.python.org/pipermail/python-dev/2003-June/036162.html

ACT IV:
I was EXPECTING this one, since it's an exceptions "gotcha" that
I already know about. Exception catching is done by object identity,
not equality. Normally, you catch with the CLASS of the instance
that got raised, so that's OK, but if you raise string exceptions
then it's got to be the same object, not just an equivalent
string.

Solution? Don't raise strings.

ACT V:
Puzzled me for a couple of minutes, but then I realized that
LookupError is an ancestor class of KeyError. That design is
a GOOD thing... if your exceptions are in a meaningful hierarchy
then you can catch whole sections of the hierarchy without having
to name each and every exception individually.

So there we have it... it took me one overnight of cogitation, but
I got them all without even cracking a reference book (although I
admit I had to fire up the interpreter to double-check how to catch
multiple exception types together).


For those who are just hungering for more, here's another (not quite
so good as Raymond's, but might confuse someone):

>>> try:
... raise IndexError
... except IndexError, errObject:
... print errObject
...

>>>

-- Michael Chermside


0 new messages