views:

5850

answers:

9

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 == "y" or ok == "Y": break 2 #this doesn't work :(
        if ok == "n" or ok == "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?

Edit-FYI: get_input is a short function I wrote that supports showing a prompt and default values and all that fanciness and returns stdin.readline().strip()

+6  A: 

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.

S.Lott
I only started learning python last night, so apologies in advance for not thinking of stuff like "ok in...". some good suggestions here, but is an exception really necessary here? There's nothing too exceptional about a user accepting input.
Matthew Scharley
Surely that code needs an "except" clause.
Robert Rossney
@monoxide - It's an exception to assumption that the user will enter a bad answer. Really, Python exceptions are cheap and useful. Don't be shy!
Just Some Guy
Seriously, exceptions are *extremely* cheap and idiomatic python uses lots and lots of them. It's very easy to define and throw custom ones, as well.
Gregg Lind
Interesting idea. I'm torn as to whether to love it or hate it.
Craig McQueen
@Craig McQueen: Ordinary Logic and Function Definitions may be a better idea than exceptions.
S.Lott
+7  A: 

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.

Matt J
I'm at college at the moment, quickly (since goto's not in the module index), what is comefrom for?
Matthew Scharley
If it is anything like the COME FROM command in INTERCAL, then nothing
1800 INFORMATION
comefrom in Python allows you to redirect the running program to a different place whenever it reaches a certain label. There's more info here (http://entrian.com/goto/)
Matt J
In other words, it's a really bad idea unless you love your spaghetti code.
Matthew Scharley
Yeah, the comefrom statement! http://www.fortran.com/come_from.html I wouldn't have expected someone to really implement it.
Federico Ramponi
As mentioned on the page I linked to, it's a quick and dirty way to alter your code flow during debug/bringup, but it's definitely not a production sort of thing.
Matt J
@Matt J: I like your use of a April Fool's farce (even if it works!), but let's be more serious. Do you really, honestly suggest using the goto module?
ΤΖΩΤΖΙΟΥ
@TZOTZIOY (sorry, don't know how to do Omega ;-): I don't purport this to be a pythonic or "clean" solution, but it *is* a paradigm that is used in other languages *when necessary*. I also suggested encapsulating the inner loop in a function, which I consider to be "better" but self-explanatory
Matt J
@Matt: cut and paste :D ΤΖΩΤΖΙΟΥ
Federico Ramponi
i like the joke, but the point of stack overflow is to promote good code, so i have to vote you down :(
Christian Oudard
I think it's a clean and readable enough solution to qualify as good code, so I vote it up. :)
J.T. Hurley
+18  A: 

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

Robert Rossney
This is another thought I had, since a get_input_yn() function would be useful elsewhere too, I'm sure.
Matthew Scharley
+1. This is a good and Pythonic approach :)
Dan
agreed in this specific case, but in the general case of 'I have nested loops, what do I do' refactoring may not make sense.
Steven Adams
+3  A: 

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 kinda like the GOTO method, provided you don't mind using an April Fool's joke module - its not Pythonic, but it does make sense.

Steven Adams
+2  A: 

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 dunno if that's a good option for you.

Jason Baker
+16  A: 

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.

John Fouhy
+1  A: 

Factor your loop logic into an iterator that yields the loop variables and returns when done -- here is a simple one that lays out images in rows/columns until we're out of images or out of places to put them:

def it(rows, cols, images):
    i = 0
    for r in xrange(rows):
        for c in xrange(cols):
            if i >= len(images):
                return
            yield r, c, images[i]
            i += 1 

for r, c, image in it(rows=4, cols=4, images=['a.jpg', 'b.jpg', 'c.jpg']):
    ... do something with r, c, image ...

This has the advantage of splitting up the complicated loop logic and the processing...

Matt Billenstein
A: 

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
yak
+1  A: 

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.

Mark Dickinson