"for" with "else"?

Stephen Horne $$$$$$$$$$$$$$$$$ at $$$$$$$$$$$$$$$$$$$$.co.uk
Wed Oct 1 20:35:18 EDT 2003


On Wed, 01 Oct 2003 18:10:40 GMT, Alex Martelli <aleax at aleax.it>
wrote:

>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.

Basically, to me, a key thing about the term 'block structured' is
about a clear and simple layout and a separation of 'layers'. The body
contained within a block structure should be distinct from that
structure - there shouldn't be bits of the block structure hidden
inside the body. If 'break' is a part of the loop, it shouldn't be
buried inside the body - it should be associated with the loop.

It's a similar principle to encapsulation, in a way.

That is the 'why' for the PEP315 discussion a little while back -
though as I said then, I'm not that much against the existing 'if x :
break' - I mainly don't like the existing proposal that means 'while x
:' may get a new and potentially confusing meaning, so wanted to
suggest an alternate syntax if some change is definitely happening.


An exception is subtly different. It's certainly a matter of
pragmatics - a raise is certainly unstructured in the sense I
described above - but that is its whole reason for existence.

When you see a loop, at least nine times out of ten it will not have a
break or a continue. The expectation, therefore, is that these hidden
exit points do not exist. When you see a try block with an exception
handler, that is a pretty strong hint that the code in the try block
may raise that exception - no matter how well hidden the raise may be.

Furthermore, exceptions are named - another useful hint to what is
going on.


Actually, 'break' can be prone to a fairly common maintenance error.
If you start with this...

  while x :
    ...
    if y :
      break;
    ...

and add an inner loop, failing to notice the hidden break, like
this...

  while x :
    ...
    while z :
      ...
      if y :  break;
      ...
    ...

...that code will fail as the break now only exits the inner loop, but
if you raise an exception, the code starts as...

  try :
    while x :
      ...
      if y :  raise blah
      ...
  except blah :
    ...

and becomes...

  try :
    while x :
      ...
      while z :
        ...
        if y :  raise blah
        ...
      ...
  except blah :
    ...

The key advantage being that the exception still exits the outer loop
as well as the inner loop, so the result is still as intended even if
the maintainer didn't notice the raise statement.

Of course this is often overkill, which is part of why break is
sometimes reasonable.

>> 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).

I tend to agree with you. I will use lambda without too much
hesitation, but at the very least I wish it had a better name.

Of course you can often define a named function just before it is
needed. For instance...

def example :
  ...
  def predicate (x) :
    return some-function-of (x)

  a = itertools.ifilter (predicate, seq)


Inline has definite advantages, and I certainly prefer list
comprehensions to combining filter and map (and lets not forget that
list comprehensions have some additional tricks up their sleeves) but
neither a lambda nor a named function is really a disaster.

>> 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.

I made a stupid mistake there - I thought the index was relevant,
which is why I mentioned enumerate and gave my own generator. I guess
I've got indices on the brain after the PEP288 and PEP322 discussions.

If the enumerate was necessary, the expression needs the ifilter, the
enumerate, and a lambda. It is the combination that can get confusing,
especially when the resulting expression needs two or more lines.

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

This is extremely compelling, I have to admit - loops that iterates
through sliding window positions (and thus for all items but one, or
whatever) can be a pain at times, and this combination of iterator and
for loop gives a very nice structure for it.

I never thought of this idea because of my historic understanding of
'for' as looping through the whole sequence. Thanks for helping me
take off those blinkers.

I'd still be tempted to say that 'if you expect it to be sorted, then
unsorted is an error' though and raise an exception when the unsorted
pair is found...

try:
  seqiter = iter(seq)
  lastone = seqiter.next()
  for item in seqiter:
    if compare_badly(item, lastone): raise unsorted
    lastone = item

  print "sorted"

catch unsorted :
  print "unsorted"


But it is just a personal preference of course, and has no advantage
at all in a trivial case like this.


BTW...

I didn't say I'd use a generator for everything - I said "In this
case" (and was wrong because I misread the case - as you say, I only
needed itertools.ifilter).

I also do not have an allergy to break - as I said, I "I tend to avoid
'break' as far as reasonable" but as you said there are times when it
is unreasonable to avoid it.

The biggest rule I have for 'break' is that the loop body must be
small enough that the break is unmissable. If the loop is that simple,
however, there is usually a simple way to handle the requirement that
doesn't need break.

If there is a simple alternative I will tend to prefer it over using
break. Therefore I rarely use break, and therefore I have never (as
far as I remember) used else on a loop. That is all I am saying. I'm
not a fundamentalist by any means.


-- 
Steve Horne

steve at ninereeds dot fsnet dot co dot uk




More information about the Python-list mailing list