197

I wanted to know if there are any built-in ways to continue to next iteration in outer loop in python. For example, consider the code:

for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            continue
    ...block1...

I want this continue statement to exit the jj loop and goto next item in the ii loop. I can implement this logic in some other way (by setting a flag variable), but is there an easy way to do this, or is this like asking for too much?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Sahas
  • 10,637
  • 9
  • 41
  • 51
  • 15
    There actually exists a working goto statement for Python: http://entrian.com/goto/. It was released as an April fool's joke :-), but is supposed to work. – codeape Dec 07 '09 at 10:17
  • 6
    Oh, please don't use that goto joke! It's remarkably clever, but you will be sad later if you put it into your code. – Ned Batchelder Dec 07 '09 at 13:35
  • I'm pretty sure this is considered by many to be one of the very very few valid use cases of goto, see here: https://stackoverflow.com/questions/24451/are-there-any-legitimate-use-cases-for-goto-in-a-language-that-supports-loops#:~:text=In%20most%20other%20languages%2C%20the,the%20particular%20piece%20of%20code. – Sidharth Ghoshal Mar 18 '23 at 19:19

9 Answers9

219
for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            break
    else:
        ...block1...

Break will break the inner loop, and block1 won't be executed (it will run only if the inner loop is exited normally).

culebrón
  • 34,265
  • 20
  • 72
  • 110
  • 1
    Hi, are there any other options like this? Because I want to do another for loop in block1, and like that my code would go 3 levels deep. Weird situation. – Sahas Dec 07 '09 at 10:24
  • 3
    To me this sounds like you are trying to do something with for loops that would be best approached in a different way... – Kimvais Dec 07 '09 at 10:27
  • Yes. That's why I didn't use the for..else structure. Now I'd still need for loops, but I'll use flag variables to divert control. – Sahas Dec 07 '09 at 11:10
  • 9
    `for...else` is often a useful construct, though it can be confusing. Just remember that `else` means "no break" in this context. – asmeurer Mar 24 '14 at 18:38
  • This seems to be limited to just two layers of loops. I need to update some code which has three nested loops, and a new customer requirement means that under certain circumstances the innermost loop need to continue next iteration of the outermost loop. I assume your suggestion would not apply to that scenario. – kasperd Apr 25 '16 at 12:29
  • More details on how this works: http://python-notes.curiousefficiency.org/en/latest/python_concepts/break_else.html#a-different-kind-of-else – btown Oct 29 '16 at 18:39
117
for i in ...:
    for j in ...:
        for k in ...:
            if something:
                # continue loop i

In a general case, when you have multiple levels of looping and break does not work for you (because you want to continue one of the upper loops, not the one right above the current one), you can do one of the following

Refactor the loops you want to escape from into a function

def inner():
    for j in ...:
        for k in ...:
            if something:
                return


for i in ...:
    inner()

The disadvantage is that you may need to pass to that new function some variables, which were previously in scope. You can either just pass them as parameters, make them instance variables on an object (create a new object just for this function, if it makes sense), or global variables, singletons, whatever (ehm, ehm).

Or you can define inner as a nested function and let it just capture what it needs (may be slower?)

for i in ...:
    def inner():
        for j in ...:
            for k in ...:
                if something:
                    return
    inner()

Use exceptions

Philosophically, this is what exceptions are for, breaking the program flow through the structured programming building blocks (if, for, while) when necessary.

The advantage is that you don't have to break the single piece of code into multiple parts. This is good if it is some kind of computation that you are designing while writing it in Python. Introducing abstractions at this early point may slow you down.

Bad thing with this approach is that interpreter/compiler authors usually assume that exceptions are exceptional and optimize for them accordingly.

class ContinueI(Exception):
    pass


continue_i = ContinueI()

for i in ...:
    try:
        for j in ...:
            for k in ...:
                if something:
                    raise continue_i
    except ContinueI:
        continue

Create a special exception class for this, so that you don't risk accidentally silencing some other exception.

Something else entirely

I am sure there are still other solutions.

user7610
  • 25,267
  • 15
  • 124
  • 150
  • 1
    Can't believe I didn't think of moving my second loop to another method. I'm getting slow – pmccallum Feb 16 '17 at 23:27
  • 4
    To me, using Exceptions is a good way of achieving this. I agree with @user7610 - "philosophically, this is what exceptions are for". – Renato Byrro Jun 23 '19 at 20:10
  • Good old boolean variable and If statements? – MrR Jun 12 '20 at 14:44
  • The OP is looking for an alternative solutions to that, "I can implement this logic in some other way (by setting a flag variable) [...]" – user7610 Jun 12 '20 at 14:58
  • There is a built-in [StopIteration exception](https://docs.python.org/3/library/exceptions.html#StopIteration). – kontur Nov 04 '20 at 09:54
  • 1
    it's pretty normal to set a flag here, although the function solution looks clearer. – fuzzyTew Nov 04 '20 at 15:30
  • 1
    @kontur StopIteration is intended for use in Python iterators (and related constructs: generators and coroutines). Reusing it here would be IMO inappropriate. – user7610 Feb 14 '21 at 10:21
  • @user7610 Ah, thanks, didn't know that, I thought it was specifically for this. :/ – kontur Feb 15 '21 at 11:45
  • "philosophically, this is what exceptions are for" is only about as true as "philosophically, drivable cars are _for_ enabling humans to move several tons of mass at high speed wherever and whenever they want". – mtraceur Jun 04 '21 at 23:54
  • ..in the context of using a car to ram down a door, because after all, the car is _able_ to put several tons of mass at high speed into the door, and it has this ability to enable its _actual_ purpose of moving people and things around, and thus clearly all means are also ends, we can generalize all necessary capability into purpose, etc. – mtraceur Jun 05 '21 at 00:04
  • Or to word it without using an analogy: no, that's not what exceptions are for. Exceptions have a much narrower purpose (a purpose which is best understood by struggling enough times to figure out how to best handle unexpected errors if you don't have exceptions, while keeping the code readable and maintainable and less prone to programmer error). It just happens that to achieve _that_ purpose, exceptions are able to bypass all structural programming constructs, in a fairly limited way actually, but with just enough flexibility to do stuff like this. – mtraceur Jun 05 '21 at 00:10
61

In other languages you can label the loop and break from the labelled loop. Python Enhancement Proposal (PEP) 3136 suggested adding these to Python but Guido rejected it:

However, I'm rejecting it on the basis that code so complicated to require this feature is very rare. In most cases there are existing work-arounds that produce clean code, for example using 'return'. While I'm sure there are some (rare) real cases where clarity of the code would suffer from a refactoring that makes it possible to use return, this is offset by two issues:

  1. The complexity added to the language, permanently. This affects not only all Python implementations, but also every source analysis tool, plus of course all documentation for the language.

  2. My expectation that the feature will be abused more than it will be used right, leading to a net decrease in code clarity (measured across all Python code written henceforth). Lazy programmers are everywhere, and before you know it you have an incredible mess on your hands of unintelligible code.

So if that's what you were hoping for you're out of luck, but look at one of the other answers as there are good options there.

David Webb
  • 190,537
  • 57
  • 313
  • 299
  • 5
    Interesting. I agree with Guido here. While it would be nice for some cases it would be abused. Another reason for not implementing it is that right now it is pretty straight forward to port code back and forth between C and Python. Once Python starts picking up features that other languages lack this becomes harder. Take for example the fact that you can have an else statement on a for loop in Python... this makes code less portable to other languages. – eric.frederich Feb 14 '13 at 17:24
  • 7
    This is more of a red-heiring than a good counter-argument, but it seems to me that the behavior of `for-else` is more complicated, more difficult to read, and probably more abused (if not an outright mistake) than named loops would be. I think I would have used a different keyword than `else` - perhaps something like `resume` would have been good? You `break` in the loop and the `resume` is right after it? – ArtOfWarfare Jul 21 '14 at 17:35
  • 7
    This makes me sad. I can't believe how I love and hate Python at the same time. So beautiful, yet so wtf. – jlh Jul 19 '17 at 20:21
  • 7
    @jlh Mostly wtf for me. Sometimes I think it wants to be different not for legitimate purpose but just to be different. This is a good example of that. I come across the need to break outer loops quite often. – Rikaelus Sep 24 '17 at 06:26
  • 1
    1. "I'm too lazy, this is too much work" 2. "Lazy programmers will abuse it" You guys think *that's* a good argument?!? – Preston Jun 25 '20 at 16:35
  • @ArtOfWarfare The `else` part of `for`-`else` executes when the loop condition stops being valid, analogous to how `else` from `if`-`else` executes when the `if` condition is false. While I do think `for`-`else` is confusing, `resume` would make no sense to me. I think `complete` would have been better, but `else` had the benefit of being an existing keyword. – jamesdlin Oct 12 '20 at 06:26
  • @ArtOfWarfare I think Guido has said he wouldn't put that in the language knowing what he knows now. – Rob Grant Dec 10 '21 at 10:55
21

I think you could do something like this:

for ii in range(200):
    restart = False
    for jj in range(200, 400):
        ...block0...
        if something:
            restart = True
            break
    if restart:
        continue
    ...block1...
asmeurer
  • 86,894
  • 26
  • 169
  • 240
  • 4
    -1: The OP clearly stated that they knew they could do something like this, and this just looks like a messier version of the accepted answer (which predates yours by 8 months, so it couldn't have been that you just missed the accepted answer). – ArtOfWarfare Jul 21 '14 at 17:31
  • 16
    The accepted answer is not clearer if you've never seen `for`, `else` before (and I think even most people who have can't remember off the top of their head how it works). – asmeurer Jul 21 '14 at 20:08
6

We want to find something and then stop the inner iteration. I use a flag system.

for l in f:
    flag = True
    for e in r:
        if flag==False:continue
        if somecondition:
            do_something()
            flag=False
Esther
  • 372
  • 5
  • 18
  • I don't know why your solution was downvoted; someone posted basically exactly the same thing and got upvoted 10 times – Locane Dec 05 '18 at 08:09
  • I'm not very lucky with stackoverflow. – Esther Dec 06 '18 at 10:24
  • 1
    Maybe because there is basically exactly the same thing posted here already, I guess... And the `False:continue` thing is... unusual formatting. As it's often the case in "natural" systems where exponential are the norm, you only have to get lucky a few times on SO to amass significant amount of reputation points. Anyways, my "best" answers are usually the least popular ones. – user7610 Mar 29 '19 at 10:39
  • @Esther couple of pointers: 1) don't use `flag == False`; it should be `flag is False` to test for equality with `True` or `False`. Also, in an if statement, you don't need to test equality like this. Just say `if not flag:` – Rob Grant Dec 10 '21 at 11:06
  • Nice one, even 'bash' have 'continue 2' to skip the outer loop – Gilles Quénot Mar 25 '23 at 07:19
6

I think one of the easiest ways to achieve this is to replace "continue" with "break" statement,i.e.

for ii in range(200):
 for jj in range(200, 400):
    ...block0...
    if something:
        break
 ...block1...       

For example, here is the easy code to see how exactly it goes on:

for i in range(10):
    print("doing outer loop")
    print("i=",i)
    for p in range(10):
        print("doing inner loop")
        print("p=",p)
        if p==3:
            print("breaking from inner loop")
            break
    print("doing some code in outer loop")
4

Another way to deal with this kind of problem is to use Exception().

for ii in range(200):
    try:
        for jj in range(200, 400):
            ...block0...
            if something:
                raise Exception()
    except Exception:
        continue
    ...block1...

For example:

for n in range(1,4):
    for m in range(1,4):
        print n,'-',m

result:

    1-1
    1-2
    1-3
    2-1
    2-2
    2-3
    3-1
    3-2
    3-3

Assuming we want to jump to the outer n loop from m loop if m =3:

for n in range(1,4):
    try:
        for m in range(1,4):
            if m == 3:
                raise Exception()            
            print n,'-',m
    except Exception:
        continue

result:

    1-1
    1-2
    2-1
    2-2
    3-1
    3-2

Reference link:http://www.programming-idioms.org/idiom/42/continue-outer-loop/1264/python

AlSub
  • 1,384
  • 1
  • 14
  • 33
Patrick
  • 51
  • 1
2

The best approach I know to continue an outer loop is using a Boolean that is scoped under the outer loop and breaking the inner one. Although, depending on the use case you may not break the inner loop, continuing an outer loop inside its inner loop implicitly suggests that you want to immediately jump to the first line of the outer loop and avoid any further execution in the inner one. That is why I added a break statement.

for i in range(0, 6):
    should_continue = False

    for f in range(1, i * 2):
        print(f"f = {f}")
        if (not (f % 1337) or not (f % 7)):
            print(f"{f} can be divided, continue outer loop")
            should_continue = True
            # leaves inner loop
            break

    if(should_continue): continue
    # Outer loop's code goes here
    print(f'Reached outer loop\ni = {i}')

This approach avoids calling any functions and dealing with possible drawbacks. Calling a function is known to be a rather expensive operation, specially for Games. Now imagine a deeply nested for loop that will run millions of times, wrapping it inside a function won't result in a smooth experience.

Wrapping the loop inside an exception block is also a bad idea and will be way slower than functions. This is because Python needs a lot of overhead to trigger the exceptions mechanism and later restore the runtime state, exceptions are designed to be used exceptionally. Taking that in mind, even some CPU optimizations such as speculative execution should not be applied to exception blocks and probably aren't.

The only "problem" I found in this approach is that break will jump once outside of the inner loop, landing on continue, which, in turn, will jump one more time. This, as opposed to a goto statement in C or JavaScript, is a bit more clumsy, but doesn't have any visible impact in performance, because it would generate only one extra instruction that runs as fast as your CPU's clock or as the interpreter implementation.

Overclocked Skid
  • 465
  • 5
  • 12
1

I just did something like this. My solution for this was to replace the interior for loop with a list comprehension.

for ii in range(200):
    done = any([op(ii, jj) for jj in range(200, 400)])
    ...block0...
    if done:
        continue
    ...block1...

where op is some boolean operator acting on a combination of ii and jj. In my case, if any of the operations returned true, I was done.

This is really not that different from breaking the code out into a function, but I thought that using the "any" operator to do a logical OR on a list of booleans and doing the logic all in one line was interesting. It also avoids the function call.

cagem12
  • 43
  • 5