How can I break out of multiple loops?

Viewed 641663

Given the following code (that doesn't work):

while True:
    # Snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok.lower() == "y": break 2 # This doesn't work :(
        if ok.lower() == "n": break

    # Do more processing with menus and stuff

Is there a way to make this work? Or do I have do one check to break out of the input loop, then another, more limited, check in the outside loop to break out all together if the user is satisfied?

35 Answers

My first instinct would be to refactor the nested loop into a function and use return to break out.

PEP 3136 proposes labeled break/continue. Guido rejected it because "code so complicated to require this feature is very rare". The PEP does mention some workarounds, though (such as the exception technique), while Guido feels refactoring to use return will be simpler in most cases.

First, ordinary logic is helpful.

If, for some reason, the terminating conditions can't be worked out, exceptions are a fall-back plan.

class GetOutOfLoop( Exception ):
    pass

try:
    done= False
    while not done:
        isok= False
        while not (done or isok):
            ok = get_input("Is this ok? (y/n)")
            if ok in ("y", "Y") or ok in ("n", "N") : 
                done= True # probably better
                raise GetOutOfLoop
        # other stuff
except GetOutOfLoop:
    pass

For this specific example, an exception may not be necessary.

On other other hand, we often have "Y", "N" and "Q" options in character-mode applications. For the "Q" option, we want an immediate exit. That's more exceptional.

First, you may also consider making the process of getting and validating the input a function; within that function, you can just return the value if its correct, and keep spinning in the while loop if not. This essentially obviates the problem you solved, and can usually be applied in the more general case (breaking out of multiple loops). If you absolutely must keep this structure in your code, and really don't want to deal with bookkeeping booleans...

You may also use goto in the following way (using an April Fools module from here):

#import the stuff
from goto import goto, label

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": goto .breakall
        if ok == "n" or ok == "N": break
    #do more processing with menus and stuff
label .breakall

I know, I know, "thou shalt not use goto" and all that, but it works well in strange cases like this.

To break out of multiple nested loops, without refactoring into a function, make use of a "simulated goto statement" with the built-in StopIteration exception:

try:
    for outer in range(100):
        for inner in range(100):
            if break_early():
                raise StopIteration

except StopIteration: pass

See this discussion on the use of goto statements for breaking out of nested loops.

keeplooping = True
while keeplooping:
    # Do stuff
    while keeplooping:
          # Do some other stuff
          if finisheddoingstuff():
              keeplooping = False

or something like that.

You could set a variable in the inner loop, and check it in the outer loop immediately after the inner loop exits, breaking if appropriate. I kind of like the GOTO method, provided you don't mind using an April Fool's joke module - it’s not Pythonic, but it does make sense.

This isn't the prettiest way to do it, but in my opinion, it's the best way.

def loop():
    while True:
    #snip: print out current state
        while True:
            ok = get_input("Is this ok? (y/n)")
            if ok == "y" or ok == "Y": return
            if ok == "n" or ok == "N": break
        #do more processing with menus and stuff

I'm pretty sure you could work out something using recursion here as well, but I don't know if that's a good option for you.

There is no way to do this from a language level. Some languages have a goto others have a break that takes an argument, python does not.

The best options are:

  1. Set a flag which is checked by the outer loop, or set the outer loops condition.

  2. Put the loop in a function and use return to break out of all the loops at once.

  3. Reformulate your logic.

Credit goes to Vivek Nagarajan, Programmer since 1987


Using Function

def doMywork(data):
    for i in data:
       for e in i:
         return 

Using flag

is_break = False
for i in data:
   if is_break:
      break # outer loop break
   for e in i:
      is_break = True
      break # inner loop break

An easy way to turn multiple loops into a single, breakable loop is to use numpy.ndindex

for i in range(n):
  for j in range(n):
    val = x[i, j]
    break # still inside the outer loop!

for i, j in np.ndindex(n, n):
  val = x[i, j]
  break # you left the only loop there was!

You do have to index into your objects, as opposed to being able to iterate through the values explicitly, but at least in simple cases it seems to be approximately 2-20 times simpler than most of the answers suggested.

Solutions in two ways

With an example: Are these two matrices equal/same?
matrix1 and matrix2 are the same size, n, two-dimensional matrices.

First solution, without a function

same_matrices = True
inner_loop_broken_once = False
n = len(matrix1)

for i in range(n):
    for j in range(n):

        if matrix1[i][j] != matrix2[i][j]:
            same_matrices = False
            inner_loop_broken_once = True
            break

    if inner_loop_broken_once:
        break

Second solution, with a function

This is the final solution for my case.

def are_two_matrices_the_same (matrix1, matrix2):
    n = len(matrix1)
    for i in range(n):
        for j in range(n):
            if matrix1[i][j] != matrix2[i][j]:
                return False
    return True

Here's an implementation that seems to work:

break_ = False
for i in range(10):
    if break_:
        break
    for j in range(10):
        if j == 3:
            break_ = True
            break
        else:
            print(i, j)

The only draw back is that you have to define break_ before the loops.

What I would personally do is use a boolean that toggles when I am ready to break out the outer loop. For example

while True:
    #snip: print out current state
    quit = False
    while True:
        ok = input("Is this ok? (y/n)")
        if ok.lower() == "y":
            quit = True
            break # this should work now :-)
        if ok.lower() == "n":
            quit = True
            break # This should work too :-)
    if quit:
        break
    #do more processing with menus and stuff

probably little trick like below will do if not prefer to refactorial into function

added 1 break_level variable to control the while loop condition

break_level = 0
# while break_level < 3: # if we have another level of nested loop here
while break_level < 2:
    #snip: print out current state
    while break_level < 1:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": break_level = 2 # break 2 level
        if ok == "n" or ok == "N": break_level = 1 # break 1 level

Trying to minimal changes to the OP's question, I just added a flag before breaking the 1st for loop and check that flag on the outer loop to see if we need to brake once again.

break_2 = False
while True:
    # Snip: print out current state
    if break_2: break
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok.lower() == "y": break_2 = True
        if break_2: break
        if ok.lower() == "n": break
    # Do more processing with menus and stuff

I came across this recently and, wanting to avoid a duplicate return statement, which can conceal logical errors, looked at @yak's idea. This works well within nested for loops but is not very elegant. An alternative is to check for the condition before the next loop:

b = None
for a in range(10):
    if something(a, b): # should never = True if b is None
        break
    for b in range(20):
        pass

This might not work everywhere but is adaptable and, if required, has the advantage of allowing the condition to be duplicated rather than a potential result.

while True:
    # Snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok.lower() == "y": 
            break_2 = True
        if ok.lower() == "n": 
            break
    if break_2:
        break

Since this question has become a standard question for breaking into a particular loop, I would like to give my answer with example using Exception.

Although there exists no label named breaking of loop in multipally looped construct, we can make use of User-defined Exceptions to break into a particular loop of our choice. Consider the following example where let us print all numbers upto 4 digits in base-6 numbering system:

class BreakLoop(Exception):
    def __init__(self, counter):
        Exception.__init__(self, 'Exception 1')
        self.counter = counter

for counter1 in range(6):   # Make it 1000
    try:
        thousand = counter1 * 1000
        for counter2 in range(6):  # Make it 100
            try:
                hundred = counter2 * 100
                for counter3 in range(6): # Make it 10
                    try:
                        ten = counter3 * 10
                        for counter4 in range(6):
                            try:
                                unit = counter4
                                value = thousand + hundred + ten + unit
                                if unit == 4 :
                                    raise BreakLoop(4) # Don't break from loop
                                if ten == 30: 
                                    raise BreakLoop(3) # Break into loop 3
                                if hundred == 500:
                                    raise BreakLoop(2) # Break into loop 2
                                if thousand == 2000:
                                    raise BreakLoop(1) # Break into loop 1

                                print('{:04d}'.format(value))
                            except BreakLoop as bl:
                                if bl.counter != 4:
                                    raise bl
                    except BreakLoop as bl:
                        if bl.counter != 3:
                            raise bl
            except BreakLoop as bl:
                if bl.counter != 2:
                    raise bl
    except BreakLoop as bl:
        pass

When we print the output, we will never get any value whose unit place is with 4. In that case, we don't break from any loop as BreakLoop(4) is raised and caught in same loop. Similarly, whenever ten place is having 3, we break into third loop using BreakLoop(3). Whenever hundred place is having 5, we break into second loop using BreakLoop(2) and whenver the thousand place is having 2, we break into first loop using BreakLoop(1).

In short, raise your Exception (in-built or user defined) in the inner loops, and catch it in the loop from where you want to resume your control to. If you want to break from all loops, catch the Exception outside all the loops. (I have not shown this case in example).

Related