When to use exceptions (was Re: reducing if statements...)

Alex Martelli aleax at aleax.it
Wed Aug 22 09:47:23 CEST 2001

"Skip Montanaro" <skip at pobox.com> writes:
>     Don> So, are there some good patterns, idioms, or whatever to provide
>     Don> guidance on when to use exceptions vs. checking error codes,
>     Don> etc. (other than the obvious general qualities: ease of use,
>     Don> readability, modifiability, ...)?
> I don't know that there are any codified examples of useful patterns for
> using exceptions vs. checking error codes.  (Alex might know.  If anyone
> would, I expect it would be him... ;-) Many modules define their own

http://www.objectarchitects.de/arcus/cookbook/exhandling/ defines a
nice, rich pattern-language for error-handling.  If you're not used
to thinking in terms of pattern *languages*, but rather of isolated
design-patterns, their paper can be a bit overwhelming.  However,
the weaving of several patterns into an integrated pattern-language,
rather than just 'cataloging' them as isolated phenomena, is really
an important step forward -- so much depends on how patterns interact
with each other, reinforce each other, and so on.

> Since Python doesn't have a goto statement or multi-level break, people
> often use flag variables to accomplish this:

Yes, alas, they do.  Hiding flow-control in data -- *AARGH*!

> This always seemed pretty clumsy to me.  I prefer to use exceptions:
>     class done(Exception): pass
>     try:
>         for i in range(len(mylist)):
>             for j in range(len(mylist[i])):
>                 if some_predicate(mylist[i][j]):
>                     raise done
>     except done:
>         do_stuff_with(mylist[i][j])
>     else:
>         print "didn't find anything interesting..."
> YMMV.  Some people hate the construct.  I think it's pretty Pythonic.

Hear, hear!

You're not going to find THIS in the error-handling pattern
language, of course -- because exceptions *in Python* are not
JUST about error-handling (again, you just have to look at
how the for-statement knows it's finished iterating -- a
classic example of an exception used in a case that is MOST
definitely NOT an error!-).

Of course, there are alternatives that may sometimes be
preferable (particularly in these days of nested scopes,
but not necessarily -- I'm using arguments here):

        def look_for(a_list, a_predicate):
            for i in range(len(a_list)):
                for j in range(len(a_list[i])):
                    if a_predicate(a_list[i][j]):
                        return i,j
            return None, None
        i, j = look_for(mylist, mypredicate)
        if i is None:
            print "nothing interesting, alas"
            do_stuff_with(mylist, i, j)

I.e., local functions are also a pretty neat control
structure builder (they sure beat control-flow that is
disguised as data, aka 'a little auxiliary flag':-).

If there's any chance that the same control-flow
pattern can be needed more than once in the same
approximate neighborhood, then hoisting it up into
a function may well be better than coding it inline
(of course, it COULD be a function that in certain
cases ALSO raises an exception -- one neat thing
about exceptions being exactly that, by default,
they propagate -- but returns sometimes suffice...).

Here, I'd have my local function look_for raise an
exception IF (at least in some cases) the enclosing
function must also terminate 'abnormally' when
nothing interesting is found.  Otherwise there's
little to choose between the above structure and:

        class notFound(Exception): pass
        def look_for(a_list, a_predicate):
            for i in range(len(a_list)):
                for j in range(len(a_list[i])):
                    if a_predicate(a_list[i][j]):
                        return i,j
            raise notFound
            i, j = look_for(mylist, mypredicate)
        except notFound:
            print "nothing interesting, alas"
            do_stuff_with(mylist, i, j)

The choice may end up depending on such minor issues
as the fact that the first form lets you code the
found and not-found case in either order (well, so
can the second, but you'd have to place the call
to do_stuff_with in the try clause rather than in
the else clause, not a crystal-clear choice maybe),
or that the second form allows binding i and j to
junk "error-indication" values.


More information about the Python-list mailing list