"for" with "else"?

Alex Martelli aleax at aleax.it
Wed Oct 1 14:10:40 EDT 2003


Stephen Horne wrote:
   ...
> As soon as I read Michael Gearys post I remembered the
> not-found-anything-in-search application, which does make some sense.
> 
> :     for item in allitems:
> :         if fraboozable(item):
> :             print "first fraboozable item is", item
> :             break
> :     else:
> :         print "Sorry, no item is fraboozable"
> 
> Personally I tend to avoid 'break' as far as reasonable, seeing it as

Me too: it's just that I think that most attempts to avoid break are
totally unreasonable, so I don't do them:-).

> unstructured - a kind of goto. As I wouldn't usually use break that
> way, I equally wouldn't use else that way either.

break is a "structured goto", no different in that respect from if/else, 
while, continue.  Or the explicit try/except StopIteration that you
appear to prefer so intensely, for that matter.

> In this case, I would make use of one of my first generators...

So, if "fraboozable(item)" stood for, say, "item**3 > 33", would
you use lambda or a named function in order to pass the "predicate"
to your generator?  I think either case is far less readable than just
expressing the condition inline (which is part of why list comprehensions
beat map and filter by a mile -- they're all expressed inline).

> This may seem odd at first, but it has a major advantage - you can
> easily continue the search from where you left off.

If I needed that, I would simply make an explicit iterator for the
items sequence, of course:

    itemsiter = iter(allitems)
    for item in itemsiter:
        if fraboozable(item):
    # and the rest as above

thus making this loop just as "continuable where it left off" as
a generator would be.  Moreover, if what I need after the first
fraboozable item is the first succeeding bambalable item, I need
no major restructuring -- I'll just use bambalable(item) in the
second "continuing" loop.

> BTW - you can get the same effect with itertools.ifilter and
> enumerate, but it's a bit of a pain.

So we disagree on this sub-topic too -- I think composing itertools
functions and such builtins as enumerate is more often a veritable
pleasure, far more fun than writing generators more often than not.
In this particular case, since the index within the sequence is not
of interest, itertools.ifilter seems clearly preferable, in particular.

But simple for loops, _with_ their due complement of break and else
as needed, are often more practical, anyway.  Often a for loop WILL in
fact run on the iterator returned by some sophisticated generator
(whether that's freshly coded, or composed from itertools bricks),
but ALSO add the small extra of a useful break just where it does
most good.

For example, consider checking whether a sequence is sorted or not
(ignoring for simplicity empty sequences):

seqiter = iter(seq)
lastone = seqiter.next()
for item in seqiter:
    if compare_badly(item, lastone):
        print "unsorted"
        break
    lastone = item
else:
    print "sorted"

where often but not always compare_badly will be > .  Writing a very
special-case generator just for this use would go, let's see, how?

def check_sorted(aniter, compare_badly=None):
    if compare_badly is None:
        def compare_badly(x, y): return x>y
    seqiter = iter(seq)
    lastone = seqiter.next()
    for item in seqiter:
        yield compare_badly(item, lastone)
        lastone = item

???  Or with a less-special-cased one such as:

def window_by_two(aniter):
    seqiter = iter(seq)
    lastone = seqiter.next()
    for item in seqiter:
        yield item, lastone
        lastone = item

???  At least the latter makes sense, since it encapsulates a useful
general concept, "windowing by 2" of a sequence.  But isn't the "one
obvious way" to use it...:

    for item, lastone in window_by_two(seq):
        if compare_badly(item, lastone):
            print "unsorted"
            break
    else:
        print "sorted"

???  Well, I guess it is only for those of us that don't suffer from
allergy to 'break' -- if one did, then maybe

badpairs = itertools.ifilter(lambda x: compare_badly(*x),
                                    window_by_two(seq))
try:
  badpairs.next()
except StopIteration:
  print "sorted"
else:
  print "unsorted"

might prove "more obvious" (???-).


Alex





More information about the Python-list mailing list