flow control and nested loops

Discussion in 'Python' started by kj, Sep 25, 2009.

  1. kj

    kj Guest

    In Perl, one can label loops for finer flow control. For example:

    X: for my $x (@X) {
    Y: for my $y (@Y) {
    for my $z (@Z) {
    next X if test1($x, $y, $z);
    next Y if test2($x, $y, $z);
    frobnicate($x, $y, $z);
    }
    glortz($x, $y);
    }
    splat($x);
    }

    What's considered "best practice" in the Python world for this sort
    of situation? The only approach I can think of requires setting
    up indicator variables that must be set and tested individually;
    e.g.

    for x in X:
    next_X = False
    for y in Y:
    next_Y = False
    for z in Z:
    if test1(x, y, z):
    next_X = True
    break
    if test2(x, y, z):
    next_Y = True
    break
    frobnicate(x, y, z)
    if next_X:
    break
    if next_Y:
    continue
    glortz(x, y)
    if next_X:
    continue
    splat(x)

    Whereas I find the Perl version reasonably readable, the Python
    one I find nearly incomprehensible. In fact, I'm not even sure
    that the Python version faithfully replicates what the Perl one is
    doing!

    Is there a better approach?

    TIA!

    kynn
    kj, Sep 25, 2009
    #1
    1. Advertising

  2. kj

    Simon Forman Guest

    On Fri, Sep 25, 2009 at 3:01 PM, kj <> wrote:
    >
    >
    > In Perl, one can label loops for finer flow control.  For example:
    >
    > X: for my $x (@X) {
    >  Y: for my $y (@Y) {
    >    for my $z (@Z) {
    >      next X if test1($x, $y, $z);
    >      next Y if test2($x, $y, $z);
    >      frobnicate($x, $y, $z);
    >    }
    >    glortz($x, $y);
    >  }
    >  splat($x);
    > }
    >
    > What's considered "best practice" in the Python world for this sort
    > of situation?  The only approach I can think of requires setting
    > up indicator variables that must be set and tested individually;
    > e.g.
    >
    > for x in X:
    >    next_X = False
    >    for y in Y:
    >        next_Y = False
    >        for z in Z:
    >            if test1(x, y, z):
    >                next_X = True
    >                break
    >            if test2(x, y, z):
    >                next_Y = True
    >                break
    >            frobnicate(x, y, z)
    >        if next_X:
    >            break
    >        if next_Y:
    >            continue
    >        glortz(x, y)
    >    if next_X:
    >        continue
    >    splat(x)
    >
    > Whereas I find the Perl version reasonably readable, the Python
    > one I find nearly incomprehensible.  In fact, I'm not even sure
    > that the Python version faithfully replicates what the Perl one is
    > doing!
    >
    > Is there a better approach?
    >
    > TIA!
    >
    > kynn
    >


    <snark>The best approach would be to reorganize your code so you
    didn't have to do that.</snark>

    Seriously though, I find both the perl and python versions
    non-obvious. You have had to use constructs like this in practice?

    Generally, I would use "flags" in tricky nested loops just like you
    did, perhaps with some comments to clarify things. An alternative
    might be to use custom exceptions. Hopefully someone smarter than me
    will come along and show an even better approach.


    class NextX(Exception): pass
    class NextY(Exception): pass


    for x in X:
    try:
    for y in Y:
    try:
    for z in Z:

    if test1(x, y, z):
    raise NextX

    if test2(x, y, z):
    raise NextY

    frobnicate(x, y, z)

    except NextY:
    continue

    glortz(x, y)

    except NextX:
    continue

    splat(x)
    Simon Forman, Sep 25, 2009
    #2
    1. Advertising

  3. kj

    Terry Reedy Guest

    kj wrote:
    >
    > In Perl, one can label loops for finer flow control. For example:
    >
    > X: for my $x (@X) {
    > Y: for my $y (@Y) {
    > for my $z (@Z) {
    > next X if test1($x, $y, $z);
    > next Y if test2($x, $y, $z);
    > frobnicate($x, $y, $z);
    > }
    > glortz($x, $y);
    > }
    > splat($x);
    > }
    >
    > What's considered "best practice" in the Python world for this sort
    > of situation? The only approach I can think of requires setting
    > up indicator variables that must be set and tested individually;
    > e.g.
    >
    > for x in X:
    > next_X = False
    > for y in Y:
    > next_Y = False
    > for z in Z:
    > if test1(x, y, z):
    > next_X = True
    > break
    > if test2(x, y, z):
    > next_Y = True
    > break
    > frobnicate(x, y, z)
    > if next_X:
    > break
    > if next_Y:
    > continue
    > glortz(x, y)
    > if next_X:
    > continue
    > splat(x)
    >
    > Whereas I find the Perl version reasonably readable, the Python
    > one I find nearly incomprehensible. In fact, I'm not even sure
    > that the Python version faithfully replicates what the Perl one is
    > doing!
    >
    > Is there a better approach?


    1. Put inner loops in a function and return instead of break.

    2. Put inner loops in
    try:
    for..
    for
    if cond: raise Something
    # or do operation that raises exception if cond is true
    except e:
    whatever

    tjr
    Terry Reedy, Sep 25, 2009
    #3
  4. On Sep 25, 12:01 pm, kj <> wrote:
    > In Perl, one can label loops for finer flow control.  For example:
    >
    > X: for my $x (@X) {
    >   Y: for my $y (@Y) {
    >     for my $z (@Z) {
    >       next X if test1($x, $y, $z);
    >       next Y if test2($x, $y, $z);
    >       frobnicate($x, $y, $z);
    >     }
    >     glortz($x, $y);
    >   }
    >   splat($x);
    >
    > }
    >
    > What's considered "best practice" in the Python world for this sort
    > of situation?  The only approach I can think of requires setting
    > up indicator variables that must be set and tested individually;
    > e.g.

    <snip>
    > Whereas I find the Perl version reasonably readable, the Python
    > one I find nearly incomprehensible.  In fact, I'm not even sure
    > that the Python version faithfully replicates what the Perl one is
    > doing!
    >
    > Is there a better approach?


    The Perl syntax is elegant and readable.
    There is not a Python direct equivalent,
    but then the situation doesn't come up often.

    For the outermost loop, a break or continue suffices.
    To exit multiple levels of loop, there a several choices
    including try/except, flags, and functions with returns.

    A try/except approach looks like this:

    class NextX(Exception):pass
    class NextY(Exception):pass

    for x in X:
    try:
    for y in Y:
    try:
    for z in Z:
    if test1(x,y,z):
    raise NextX
    if test2(x,y,z):
    raise NextY
    frobnicate(x,y,z)
    except NextY: pass
    except NextX: pass


    Another approach for exiting multiple levels of loops is wrap the
    inner calls in a function and return from them when needed:

    def f(x):
    for y in y:
    for z in Z:
    if test1(x,y,z):
    return
    frobnicate(x,y,z)

    for x in X:
    f(x)

    Or you can write a little state machine with flags and a single loop
    but that isn't very readable or satisfying.


    Raymond
    Raymond Hettinger, Sep 26, 2009
    #4
  5. kj

    Bearophile Guest

    Raymond Hettinger:

    > Another approach for exiting multiple levels of loops is wrap the
    > inner calls in a function and return from them when needed:
    >
    >    def f(x):
    >        for y in y:
    >            for z in Z:
    >                if test1(x,y,z):
    >                    return
    >                frobnicate(x,y,z)
    >
    >    for x in X:
    >       f(x)


    That's usual the solution I use for this problem, it's a little rough
    and it has some performance cost, but it's readable and simple.

    In PHP break and continue accept an optional numeric value (default is
    1, 0 is of course ignored) "which tells it how many nested enclosing
    structures are to be broken out of.":
    http://us2.php.net/manual/en/control-structures.break.php
    http://us2.php.net/manual/en/control-structures.continue.php
    That PHP design gives me shivers, it's horrid, bug-prone and fragile:

    for x in X:
    for y in Y:
    for z in Z:
    if test1(x, y, z):
    continue 3
    if test2(x, y, z):
    continue 2
    frobnicate(x, y, z)
    glortz(x, y)
    splat(x)


    A better solution is to add labels to Python (I hope this code is
    equivalent to the original Perl one), that can optionally be used by
    "continue" and "break". This solution design is also used by D:

    label OUTER:
    for x in X:
    label INNER:
    for y in Y:
    for z in Z:
    if test1(x, y, z):
    continue OUTER
    if test2(x, y, z):
    continue INNER
    frobnicate(x, y, z)
    glortz(x, y)
    splat(x)

    Bye,
    bearophile
    Bearophile, Sep 26, 2009
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Allan Bruce

    dynamic number of nested loops

    Allan Bruce, Jul 1, 2004, in forum: Java
    Replies:
    5
    Views:
    9,724
    Tor Iver Wilhelmsen
    Jul 3, 2004
  2. Porthos
    Replies:
    3
    Views:
    474
    Joris Gillis
    Feb 7, 2005
  3. Jack Dowson
    Replies:
    0
    Views:
    445
    Jack Dowson
    May 7, 2007
  4. Phlip
    Replies:
    4
    Views:
    354
    red floyd
    Sep 10, 2007
  5. Me
    Replies:
    2
    Views:
    229
Loading...

Share This Page