417

Is there an easier way to break out of nested loops than throwing an exception? (In Perl, you can give labels to each loop and at least continue an outer loop.)

for x in range(10):
    for y in range(10):
        print x*y
        if x*y > 50:
            "break both loops"

I.e., is there a nicer way than:

class BreakIt(Exception): pass

try:
    for x in range(10):
        for y in range(10):
            print x*y
            if x*y > 50:
                raise BreakIt
except BreakIt:
    pass
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michael Kuhn
  • 8,302
  • 6
  • 26
  • 27

8 Answers8

983
for x in xrange(10):
    for y in xrange(10):
        print x*y
        if x*y > 50:
            break
    else:
        continue  # only executed if the inner loop did NOT break
    break  # only executed if the inner loop DID break

The same works for deeper loops:

for x in xrange(10):
    for y in xrange(10):
        for z in xrange(10):
            print x,y,z
            if x*y*z == 30:
                break
        else:
            continue
        break
    else:
        continue
    break
Bob Stein
  • 16,271
  • 10
  • 88
  • 101
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
  • 38
    For an explanation on this: http://psung.blogspot.com.au/2007/12/for-else-in-python.html – aiham Apr 16 '12 at 10:44
  • 14
    Because of the required continue statement for the outer loop this generally does not work well in situations where the nested loop is not the only code in the outer for loop. The OP example might be too simple. – Anthon Oct 04 '12 at 06:39
  • 14
    You could replace the `continue` with `ok = True`, and `break` with `if not ok: break`. – Markus Jarderot Oct 04 '12 at 07:05
  • 4
    At first I thought the `else` meant the block is executed when there are no iterations. Good to know! – chaz Oct 06 '13 at 01:45
  • 1
    Won't work if there is already an else statement in use in the inner loop – embert Jan 14 '14 at 12:16
  • This won't work if the inner loop is infinite (e.g. `for x in itertools.count(1): ...`). – Eugene Yarmash Mar 23 '14 at 22:59
  • 4
    @eugeney It will work as intended. In the case of infinite sequences, it will never reach the `continue`. If it doesn't make sense to use this technique in your situation, don't. – Markus Jarderot Mar 23 '14 at 23:27
  • 1
    This solution makes it impossible to break the inner loop without breaking all. – Alexey Nov 01 '17 at 10:10
  • 2
    Key insight: **If the inner loop does not break, the outer loop will not either.** The for-else clause [only happens](https://stackoverflow.com/a/9980752/673991) if the inner loop does _not_ break. Then `continue` avoids the outer break too. – Bob Stein Apr 16 '18 at 16:02
  • 1
    I'm not convinced the confusion added by this method is worth it just to avoid adding a boolean. – Shiania White May 23 '19 at 14:43
  • 2
    Overloading the `else` keyword for this semantic is unfortuate. First, when one sees an `else:` statement, their first inclination is to assume it is an else block for an if-statement. More importantly, it doesn't make grammatical sense. It would have been much more intelligable had the creator chosen something like `andalso:` for : andalso this-if-loop-doesn't-break. Oh well... Opportunity lost. – Keith Hanlan May 31 '19 at 01:49
  • Only use it when there is only one inner `for` loop. – Itachi Jan 20 '21 at 02:39
  • The comments are actually true in this case, but not in a general sense, as the continue is also executed in case the inner loop reaches the end. I suggest changing them as here https://stackoverflow.com/questions/189645/how-to-break-out-of-multiple-loops : "# Continue if the inner loop wasn't broken." and "# Inner loop was broken, break the outer. – aless80 Mar 30 '21 at 09:27
  • 1
    How is this more clean than `break: LABEL` ?! /facepalm – Edy Bourne Jul 18 '21 at 21:44
210

It has at least been suggested, but also rejected. I don't think there is another way, short of repeating the test or re-organizing the code. It is sometimes a bit annoying.

In the rejection message, Mr van Rossum mentions using return, which is really sensible and something I need to remember personally. :)

unwind
  • 391,730
  • 64
  • 469
  • 606
  • 24
    I second using the `return` statement. It forced me to write the inner loop in a second function, but made the code much easier to understand. – vdboor Sep 11 '12 at 10:29
  • This generic solution that also works when the nested for loop is followed by other statements. E.g. when looping over a list of sentences and using several for loops to filter out specific sentences based on existence of specific words or numbers, before doing the actual work at the end of the outer for loop. – Anthon Oct 04 '12 at 06:51
  • 22
    Mr van Rossum's solution doesn't work when you are in a generator, which is a pretty big hole IMHO – raph.amiard Feb 26 '14 at 20:55
90

If you're able to extract the loop code into a function, a return statement can be used to exit the outermost loop at any time.

def foo():
    for x in range(10):
        for y in range(10):
            print(x*y)
            if x*y > 50:
                return
foo()

If it's hard to extract that function you could use an inner function, as @bjd2385 suggests, e.g.

def your_outer_func():
    ...
    def inner_func():
        for x in range(10):
            for y in range(10):
                print(x*y)
                if x*y > 50:
                    return
    inner_func()
    ...
Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
62

Use itertools.product!

from itertools import product
for x, y in product(range(10), range(10)):
    #do whatever you want
    break

Here's a link to itertools.product in the python documentation: http://docs.python.org/library/itertools.html#itertools.product

You can also loop over an array comprehension with 2 fors in it, and break whenever you want to.

>>> [(x, y) for y in ['y1', 'y2'] for x in ['x1', 'x2']]
[
    ('x1', 'y1'), ('x2', 'y1'),
    ('x1', 'y2'), ('x2', 'y2')
]
John R Perry
  • 3,916
  • 2
  • 38
  • 62
Fábio Santos
  • 3,899
  • 1
  • 26
  • 31
  • 3
    Gotta do the one liner: >>> print "\n".join(map(str,takewhile(lambda i: i <= 50,(x*y for x,y in product(xrange(10), xrange(10)))))) – Rusty Rob Mar 21 '13 at 22:55
  • 5
    this doesn't address the main issue of breaking nested loops in full generality – Rabih Kodeih Aug 01 '13 at 01:45
  • 1
    Only if you are looking for a way to break out of one loop at a time, but still be able to break out of both. For that, you can use a function, or an exception. I find this method more elegant when you don't need to break out of one of the loops at a time. – Fábio Santos Aug 05 '13 at 13:49
  • 2
    This is good for the simple case, but not for any case where the second range is dependent on the first. – Wyrmwood Jan 19 '17 at 17:50
  • The second solution can be used when the second range depends on the first. IE `y` is in scope to the right of `for y in [...]`. Not sure how pythonic that is, I haven't written much python these past few years! – Fábio Santos Oct 29 '20 at 10:21
51

Sometimes I use a boolean variable. Naive, if you want, but I find it quite flexible and comfortable to read. Testing a variable may avoid testing again complex conditions and may also collect results from several tests in inner loops.

    x_loop_must_break = False
    for x in range(10):
        for y in range(10):
            print x*y
            if x*y > 50:
                x_loop_must_break = True
                break
        if x_loop_must_break: break
Halberdier
  • 1,164
  • 11
  • 15
29

If you're going to raise an exception, you might raise a StopIteration exception. That will at least make the intent obvious.

dbn
  • 13,144
  • 3
  • 60
  • 86
  • 4
    This may have unexpected side effects if your loop is part of a larger yield construct and you don't catch this well – Konrads Sep 15 '20 at 02:17
8

You can also refactor your code to use a generator. But this may not be a solution for all types of nested loops.

J S
  • 1,057
  • 7
  • 6
3

In this particular case, you can merge the loops with a modern python (3.0 and probably 2.6, too) by using itertools.product.

I for myself took this as a rule of thumb, if you nest too many loops (as in, more than 2), you are usually able to extract one of the loops into a different method or merge the loops into one, as in this case.

Tetha
  • 4,826
  • 1
  • 16
  • 17