?

Log in

No account? Create an account

Sun, May. 31st, 2009, 09:52 pm
Maybe goto isn't so bad after all.

I've used this pattern a lot over the last few days:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            break
    else:
        continue
    break
else:
    return
do stuff with eggs and jello


People familiar with my code will find this amount of complex control flow unsurprising. But the reasoning here is different from my past practices. In the past I always optimized to have as few lines of code as possible, regardless of how upside down and backwards it made everything be organized, but that could be done much better in this case like so:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            do stuff with eggs and jello
            return


There are two reasons I find this construct objectionable. First is that it leads to a lot of code having a lot of indentation (the 'do stuff' is in many cases dozens of lines) which isn't good for readability. Second is that the natural flow of the code is 'scan for the thing I want, and then do something with it' which is the flow of the structure I've been using. I suppose one could write it like this:

def my_little_func():
    for eggs in spam:
        for jello in eggs:
            if jello is the one I want:
                return eggs, jello
    return None, None

eggs, jello = my_little_func()
if eggs is None:
    return
do stuff with eggs and jello


But that's hardly elegant and forces the flow to jump around in terms of the visual layout of the code, which goes against what I'm trying to accomplish.

At the risk of starting a religious war, I'd like to point out that the most readable and versatile way of doing what I want is like so:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            goto x
return
x: do stuff with eggs and jello


But short of that the following may be the best option currently available. I might switch to it:

for eggs in spam:
    for jello in eggs:
        if jello is the one I want:
            after_func(eggs, jello)
            return

def after_func(eggs, jello):
    do stuff with eggs and jello


But it still doesn't look as nice as the version with a goto.

Mon, Jun. 1st, 2009 02:15 pm (UTC)
agthorr

Here's another variation:

def iterspam(spam):
    for eggs in spam:
        for jello in eggs:
            yield eggs, jello

for eggs, jello in iterspam(spam):
    if i_want(jello):
        break
else:
    return
do stuff with eggs and jello

Tue, Jun. 2nd, 2009 06:54 am (UTC)
manuzhai

That one is nice. Also, for the not-so-bad case, you may want to inverse the nested condition to make it continue if it doesn't match what you're looking for. So

for i in range(5):
    if i == 3:
        do(i)
        things(i)
        and(i)
        some(i)
        more(i)


becomes

for i in range(3):
    if i != 3:
        continue
    do(i)
    things(i)
    and(i)
    some(i)
    more(i)