Working around a lack of 'goto' in python

Roy Smith roy at panix.com
Tue Mar 9 16:05:06 EST 2004


Joe Mason <joe at notcharles.ca> wrote:

> In article <roy-F325DF.12255009032004 at reader2.panix.com>, Roy Smith wrote:
> > OK, I'll bite.  What's wrong with exceptions for breaking out of deeply 
> > nested loops?
> 
> Philosophically, the termination condition of a loop isn't an
> exceptional circumstance.

The normal flow of control is when you fall out the bottom of the loop 
when your control test goes false.  Breaking out of the middle is indeed 
an exceptional circumstance; it is an exception to the normal flow of 
control.  People tend to think of exceptions as errors, but they don't 
have to be.

Now, to be fair, imagine a construct like this:

try:
  for i in range (iMax):
     for j in range (jMax):
       for k in range (kMax):
          if thisIsIt (i, j, k):
             raise IFoundIt
   print "it's not there"
except IFoundIt:
   print "yes it is"

Presumably the expectation is that the thing you're looking for will 
exist somewhere in the i,j,k coordinate space.  Falling out the bottom 
of the loop is now the error condition.  I think this is what you were 
getting at, and I agree that this turns the whole concept of an 
exception on its ear.  The example above is one where I think factoring 
it out into a function makes the most sense:

def whereIsTheThing ():
   for i in range (iMax):
     for j in range (jMax):
       for k in range (kMax):
          if thisIsIt (i, j, k):
            return (i, j, k)

   raise ItsNotThere
 
> Practically, exceptions are overkill.  Using named break (or faking it
> with goto) is "static multi-level exit", while an exception is dynamic.
> It takes more overhead at runtime

That's certainly true in C++.  In complied/static languages like C++, 
there can be a significant speed difference between things that can be 
done at compile time and things that need to be done at run time (like 
constructing exception objects).

In Python, the cost of using exceptions is way less.  I just tried a 
little test, timing a bunch of breaks vs raises to get out of an inner 
loop (code listing below).  My timing results were:

count = 10000, the break way took 7.349000 seconds
count = 10000, the exception way took 8.121592 seconds

Yeah, the exceptions were slower, but only 10% slower, and that's for a 
loop that did essentially no useful work, so it's all overhead.  The 
difference would be smaller if the loop did anything real.  I'm not 
likely to let such a small change in execution time drive my code 
design.  If I was worried about such small speed gains, I wouldn't be 
writing in Python.

I would try repeating this example in C++, but the thought of how many 
lines of code I'd have to write to do it has convinced me that it should 
be left as "an excercise for the reader" :-)

> and if you don't catch it right it
> can escape up the call stack.

Well, yeah, but the answer to that is you catch it.  In fact, that's one 
of the nice things about exceptions.  If you don't catch it correctly, 
you find out about it fast.  If you mis-code a goto or break label, you 
just quietly execute the wrong code.

> A more pragmatic argument is that, for C++,
[...]
>   // declare an EndLoopException class, which I forget how to do because
>   // I haven't used C++ exceptions in ages

I think what you've demonstrated here is not so much that exceptions are 
a bad thing, but that C++'s implementation of exceptions (like most 
things in C++) is overly complicated.  I find it interesting that many 
people who come from a C++ background (especially those who migrated 
from C) tend to dislike exceptions.  It must have been something in the 
water when they were growing up :-)

--------------

#!/usr/bin/env python

import time

class Break (Exception):
    pass

x = 10000

t0 = time.time ()
count = 0
for i in range (x):
    for j in range (x):
        break
    count += 1

t1 = time.time ()
print "count = %d, the break way took %f seconds" % (count, t1 - t0)

t0 = time.time ()
count = 0
for i in range (x):
    try:
        for j in range (x):
            raise Break
    except Break:
        pass
    count += 1

t1 = time.time ()
print "count = %d, the exception way took %f seconds" % (count, t1 - t0)



More information about the Python-list mailing list