Sunday 25 March 2012 — This is close to 13 years old. Be careful.
This is a question that crops up often:
I have two nested loops, and inside, how can I break out of both loops at once?
Python doesn’t offer a way to break out of two (or more) loops at once, so the naive approach looks like this:
done = False
for x in range(10):
for y in range(20):
if some_condition(x, y):
done = True
break
do_something(x, y)
if done:
break
This works, but seems unfortunate. A lot of noise here concerns the breaking out of the loop, rather than the work itself.
The sophisticated approach is to get rid of, or at least hide away, the double loop. Looked at another way, this code is really iterating over one sequence of things, a sequence of pairs. Using Python generators, we can neatly encapsulate the pair-ness, and get back to one loop:
def pairs_range(limit1, limit2):
"""Produce all pairs in (0..`limit1`-1, 0..`limit2`-1)"""
for i1 in range(limit1):
for i2 in range(limit2):
yield i1, i2
for x, y in pairs_range(10, 20):
if some_condition(x, y):
break
do_something(x, y)
Now our code is nicely focused on the work at hand, and the mechanics of the double loop needed to produce a sequence of pairs is encapsulated in pairs_range.
Naturally, pairs_range could become more complex, more interesting ranges, not just pairs but triples, etc. Adapt to your own needs.
As with any language, you can approach Python as if it were C/Java/Javascript with different syntax, and many people do at first, relying on concepts they already know. Once you scratch the surface, Python provides rich features that take you off that track. Iteration is one of the first places you can find your Python wings.
Comments
However I still think this code is super ugly. This is better: I think that's the right way to do this. Double loops are evil.
@gruszczy: putting the loops into a function is another option, true, though I'm not sure I find your solution better, since it would break up the larger code that this loop is in, without abstracting the double loop from the work in the loop.
The situation would be a lot different if the whole clause was evaluated lazily (ie. a la Haskell). In that case the environment would take care of little perf tweaks such as this.
The cases I've encountered personally were all certain bits of processing inside the outer loop (before the inner one), so abstracting the iteration away wouldn't have worked. Extracting the double loop into a function and using 'return' for early exit worked fine.
@Marius, indeed, the actual code, and the extra complexity real code would introduce, will heavily influence the solution chosen.
Thanks, everyone, for adding ideas!
The trick is to use the else-clause of the for loop. The else-clause is executed when a loop terminates normally, but is skipped on a 'break'.
As shown below, it can also be used for more deeply nested loops: @Grant: zip() does not provide the desired functionality. For
zip(range(10), range(20)), x and y would take on the sequence (0,0), (1,1), (2,2), ... (9,9).
For the generator expression, itertools.product, or the nested loops shown above, x and y take on the sequence: (0,0), (0,1), (0,2), ... (1,0), (1,1), ... (9,19).
Add a comment: