How to break out of multiple loops?
Solution 1:
My first instinct would be to refactor the nested loop into a function and use return
to break out.
Solution 2:
Here's another approach that is short. The disadvantage is that you can only break the outer loop, but sometimes it's exactly what you want.
for a in xrange(10):
for b in xrange(20):
if something(a, b):
# Break the inner loop...
break
else:
# Continue if the inner loop wasn't broken.
continue
# Inner loop was broken, break the outer.
break
This uses the for / else construct explained at: Why does python use 'else' after for and while loops?
Key insight: It only seems as if the outer loop always breaks. But if the inner loop doesn't break, the outer loop won't either.
The continue
statement is the magic here. It's in the for-else clause. By definition that happens if there's no inner break. In that situation continue
neatly circumvents the outer break.
Solution 3:
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.
Solution 4:
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.
Solution 5:
I tend to agree that refactoring into a function is usually the best approach for this sort of situation, but for when you really need to break out of nested loops, here's an interesting variant of the exception-raising approach that @S.Lott described. It uses Python's with
statement to make the exception raising look a bit nicer. Define a new context manager (you only have to do this once) with:
from contextlib import contextmanager
@contextmanager
def nested_break():
class NestedBreakException(Exception):
pass
try:
yield NestedBreakException
except NestedBreakException:
pass
Now you can use this context manager as follows:
with nested_break() as mylabel:
while True:
print "current state"
while True:
ok = raw_input("Is this ok? (y/n)")
if ok == "y" or ok == "Y": raise mylabel
if ok == "n" or ok == "N": break
print "more processing"
Advantages: (1) it's slightly cleaner (no explicit try-except block), and (2) you get a custom-built Exception
subclass for each use of nested_break
; no need to declare your own Exception
subclass each time.