for: else: - any practical uses for the else clause?

S

skip

metaperl> I'm wondering if anyone has ever found a practical use for the
metaperl> else branch?

Yeah, I use it from time to time:

for foo in bar:
if foo matches some condition:
print "sail to tahiti!"
break
else:
print "abandon ship!"

Skip
 
A

Amaury Forgeot d'Arc

B

Ben Sizer

metaperl> I'm wondering if anyone has ever found a practical use for the
metaperl> else branch?

Yeah, I use it from time to time:

for foo in bar:
if foo matches some condition:
print "sail to tahiti!"
break
else:
print "abandon ship!"

As a C++ programmer (which I'm sure undermines my argument before
you've even read it...), this feels 'backwards' to me. Although I am no
purist, the 'else' typically implies failure of a previous explicit
condition, yet in this case, it's executed by default, when the
previous clause was successfully executed. It would seem more natural
if the else clause was triggered by 'bar' being empty, or even if the
loop was explicitly broken out of, though I'm sure that would make the
construct much less useful.
 
S

Sion Arrowsmith

Ben Sizer said:
Yeah, I use it from time to time:

for foo in bar:
if foo matches some condition:
print "sail to tahiti!"
break
else:
print "abandon ship!"

As a C++ programmer (which I'm sure undermines my argument before
you've even read it...), this feels 'backwards' to me. Although I am no
purist, the 'else' typically implies failure of a previous explicit
condition, yet in this case, it's executed by default, when the
previous clause was successfully executed. It would seem more natural
if the else clause was triggered by 'bar' being empty, [ ... ]

It does:
.... print foo
.... else:
.... print 'else'
....
else

I think it's clearer to see by comparing while with if:

if False:
do nothing
else:
do something

while False:
do nothing
else:
do something

and getting to the for behaviour from while is trivial.

That said, I've not had much call for it and was kind of surprised to
find myself writing a genuine for ... else the other week. But it was
the obvious way to do the task at hand, and I was happy it was there.
 
M

metaperl

Actually right after posting this I came up with a great usage. I use
meld3 for my Python based dynamic HTML generation. Whenever I plan to
loop over a tree section I use a for loop, but if there is no data to
iterate over, then I simply remove that section from the tree or
populate it with a "no data" message.
 
K

Klaas

metaperl said:
Actually right after posting this I came up with a great usage. I use
meld3 for my Python based dynamic HTML generation. Whenever I plan to
loop over a tree section I use a for loop, but if there is no data to
iterate over, then I simply remove that section from the tree or
populate it with a "no data" message.

else: does not trigger when there is no data on which to iterate, but
when the loop terminated normally (ie., wasn't break-ed out). It is
meaningless without break.

Python 2.4.3 (#1, Mar 29 2006, 15:37:23)
[GCC 4.0.2 20051125 (Red Hat 4.0.2-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
.... print 'nothing'
.... else:
.... print 'done'
....
done

-Mike
 
K

Klaas

Klaas said:
else: does not trigger when there is no data on which to iterate, but
when the loop terminated normally (ie., wasn't break-ed out). It is
meaningless without break.

Sorry, this was worded confusingly. "else: triggers when the loop
terminates normally, not simply in the case that there is no iterated
data".

-Mike
 
M

Mike Klaas

The else clause *is* executed when there is no data on which to iterate.
Your example even demonstrates that clearly:

Yes--there is a missing "just" in that sentence.

-Mike
 
?

=?ISO-8859-1?Q?BJ=F6rn_Lindqvist?=

I agree that it is meaningless without a break statement, but I still find
it useful when I want to determine whether I looped over the whole list or
not. For example, if I want to see whether or not a list contains an odd
number:

for i in list:
if i % 2 == 1:
print "Found an odd number."
break
else:
print "No odd number found."

Without the else clause I would need to use an extra variable as a "flag"
and check its value outside the loop:

You can use generator comprehension:

if (i for i in list if i % 2 == 1):
print "Found an odd number."
else:
print "No odd number found."

I *think* any() should also work:

if any(i % 2 == 1 in list):
....

And so on. For every use of the for/else clause there exists a better
alternative. Which sums up my opinion about the construct -- if you
are using it, there's something wrong with your code.
 
M

Matthew Woodcraft

And so on. For every use of the for/else clause there exists a better
alternative. Which sums up my opinion about the construct -- if you
are using it, there's something wrong with your code.

How do you transform this?

height = 0
for block in stack:
if block.is_marked():
print "Lowest marked block is at height", height
break
height += block.height
else:
raise SomeError("No marked block")

-M-
 
P

Paul Rubin

Matthew Woodcraft said:
How do you transform this?

height = 0
for block in stack:
if block.is_marked():
print "Lowest marked block is at height", height
break
height += block.height
else:
raise SomeError("No marked block")

Untested:

all_heights = [block.height for block in stack if block.is_marked()]
if all_heights:
height = sum(all_heights)
else:
raise SomeError("No marked block")

Alternatively (lower memory usage for large list):

all_heights = (block.height for block in stack if block.is_marked())
try:
height = all_heights.next()
height += sum(all_heights)
except StopIteration:
raise SomeError("No marked block")
 
S

Sybren Stuvel

Paul Rubin enlightened us with:
height = 0
for block in stack:
if block.is_marked():
print "Lowest marked block is at height", height
break
height += block.height
else:
raise SomeError("No marked block")

all_heights = [block.height for block in stack if block.is_marked()]
if all_heights:
height = sum(all_heights)
else:
raise SomeError("No marked block")

Alternatively (lower memory usage for large list):

all_heights = (block.height for block in stack if block.is_marked())
try:
height = all_heights.next()
height += sum(all_heights)
except StopIteration:
raise SomeError("No marked block")

I must say that the for/else construct is a LOT more readable than the
rewritten alternatives.

Sybren
 
M

MonkeeSage

Sybren said:
I must say that the for/else construct is a LOT more readable than the
rewritten alternatives.

+1

I just wish it had a more intuitive name like "after:" or "then:", as
"else:" seems like a choice between the loop and the other block (but
really the choice is between StopIteration and break).

Regards,
Jordan
 
P

Paul Rubin

Sybren Stuvel said:
I must say that the for/else construct is a LOT more readable than the
rewritten alternatives.

They are all pretty ugly. I prefer the genexp version with a
hypothetical "is_empty" function:

all_heights = (block.height for block in stack if block.is_marked())
if is_empty(all_heights):
raise SomeError("No marked block")
heights = sum(all_heights)

Python generators don't really work the right way for this though.

I've been fooling around with Haskell, so these kinds of constructions
seems more natural to me than explicit for loops.
 
P

Peter Otten

Sybren said:
Paul Rubin enlightened us with:
height = 0
for block in stack:
if block.is_marked():
print "Lowest marked block is at height", height
break
height += block.height
else:
raise SomeError("No marked block")

all_heights = [block.height for block in stack if block.is_marked()]
if all_heights:
height = sum(all_heights)
else:
raise SomeError("No marked block")

Alternatively (lower memory usage for large list):

all_heights = (block.height for block in stack if block.is_marked())
try:
height = all_heights.next()
height += sum(all_heights)
except StopIteration:
raise SomeError("No marked block")

I must say that the for/else construct is a LOT more readable than the
rewritten alternatives.

I like

def blocks_til_mark(stack):
for block in stack:
if block.is_marked():
return
yield block
raise SomeError
height = sum(block.height for block in blocks_til_mark(stack))

Peter
 
P

Peter Otten

Paul said:
They are all pretty ugly. I prefer the genexp version with a
hypothetical "is_empty" function:

all_heights = (block.height for block in stack if block.is_marked())
if is_empty(all_heights):
raise SomeError("No marked block")

Such a function would have to rebind the generator:

def check_empty(items):
items = iter(items)
try:
first = items.next()
except StopIteration:
return False
return chain([first], items)

all_heights = check_empty(all_heights)
if not all_heights:
raise SomeError
heights = sum(all_heights)

Python generators don't really work the right way for this though.

You can make it work, but the result tends to be messy:

from itertools import chain

def raiseiter(exc, *args):
raise exc(*args)
yield None # unreachable
def stop():
raise StopIteration

height = sum(block.height for block in chain(stack, raiseiter(SomeError)) if
(not block.is_marked()) or stop())

Peter
 
P

Paul Rubin

Peter Otten said:
I like

def blocks_til_mark(stack):
for block in stack:
if block.is_marked():
return
yield block
raise SomeError
height = sum(block.height for block in blocks_til_mark(stack))

Oh my, I realize now that I mis-read the original, and my examples
were all wrong. Shows how the explicit loop isn't necessarily so
clear ;-). Note that unlike the original, the loop above fails to set
height = 0 in the case where the exception gets raise.

I'd maybe just scan the stack twice. The rescan is needed only if
there can be blocks with height <= 0. Otherwise, you can just raise
SomeError if height == 0:

height = sum(b.height for b in
itertools.takewhile(lambda: not block.is_marked(), stack))

if height == 0 and True not in (b.is_marked() for b in stack):
raise SomeError
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,173
Latest member
GeraldReund
Top