Breaking out of nested loops [duplicate]
Asked Answered
E

8

438

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
Electrochemistry answered 17/3, 2009 at 9:24 Comment(5)
You could also import "sys" and when you want to end the program write sys.exit()Mechanistic
@Mechanistic Not if you want to do something afterward.Rerun
isBreak = False for x in range(1,4): if(isBreak): break for y in range(1,10): if(xy>16): isBreak = True break print(xy) if(x<3): print("********")Wells
Inability to break/continue multiple loops is a fly in the ointment of the Python syntaxZampino
Check out this answer to a similar question: https://mcmap.net/q/55589/-how-can-i-break-out-of-multiple-loops . It suggests using context managers.Agnomen
C
216

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. :)

Clementia answered 17/3, 2009 at 9:27 Comment(4)
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.Treadmill
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.Crackup
Mr van Rossum's solution doesn't work when you are in a generator, which is a pretty big hole IMHOGrindlay
@Grindlay yield from may help decomposing generatorsZampino
O
1018
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
Onega answered 17/3, 2009 at 12:27 Comment(14)
For an explanation on this: psung.blogspot.com.au/2007/12/for-else-in-python.htmlEthiopian
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.Crackup
You could replace the continue with ok = True, and break with if not ok: break.Onega
At first I thought the else meant the block is executed when there are no iterations. Good to know!Holocene
Won't work if there is already an else statement in use in the inner loopGloaming
This won't work if the inner loop is infinite (e.g. for x in itertools.count(1): ...).Dockery
@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.Onega
This solution makes it impossible to break the inner loop without breaking all.Agnomen
Key insight: If the inner loop does not break, the outer loop will not either. The for-else clause only happens if the inner loop does not break. Then continue avoids the outer break too.Seism
I'm not convinced the confusion added by this method is worth it just to avoid adding a boolean.Dupuy
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 <loop>: andalso this-if-loop-doesn't-break. Oh well... Opportunity lost.Levite
Only use it when there is only one inner for loop.Citystate
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 #190145 : "# Continue if the inner loop wasn't broken." and "# Inner loop was broken, break the outer.Tralee
How is this more clean than break: LABEL ?! /facepalmAngulate
C
216

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. :)

Clementia answered 17/3, 2009 at 9:27 Comment(4)
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.Treadmill
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.Crackup
Mr van Rossum's solution doesn't work when you are in a generator, which is a pretty big hole IMHOGrindlay
@Grindlay yield from may help decomposing generatorsZampino
T
94

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()
    ...
Tonnie answered 17/3, 2009 at 12:37 Comment(0)
A
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')
]
Abstractionist answered 26/10, 2011 at 17:47 Comment(5)
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))))))Paloma
this doesn't address the main issue of breaking nested loops in full generalityRoumell
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.Easton
This is good for the simple case, but not for any case where the second range is dependent on the first.Painstaking
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!Easton
D
53

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
Doeskin answered 16/5, 2011 at 20:50 Comment(1)
Simple and working solution! I used inner_break=False with while not inner_break and condition: ... for-loop: if break_condition: inner_break=True ...Kolinsky
S
29

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

Shelton answered 14/8, 2012 at 0:31 Comment(1)
This may have unexpected side effects if your loop is part of a larger yield construct and you don't catch this wellPe
D
8

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

Diffuser answered 17/3, 2009 at 10:29 Comment(0)
U
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.

Uncommitted answered 17/3, 2009 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.