for / while else doesn't make sense

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jun 12 06:06:38 EDT 2016


On Sunday 12 June 2016 17:01, pavlovevidence at gmail.com wrote:

> On Thursday, May 19, 2016 at 9:43:56 AM UTC-7, Herkermer Sherwood wrote:
>> Most keywords in Python make linguistic sense, but using "else" in for and
>> while structures is kludgy and misleading. I am under the assumption that
>> this was just utilizing an already existing keyword. Adding another like
>> "andthen" would not be good.
[...]
>> I think perhaps "finally" should be added to for and while to do the same
>> thing as "else". What do you think?
> 
> I agree it's not the clearest name, but it does behave consistent with
> if...else.  

Er, not really.

if condition:
   print "A"
else:
   print "B"


Exactly one of "A" or "B", but NEVER both, will be printed.

for item in sequence:
    print "A"
else:
    print "B"

Normally we would expect that both "A" and "B" will be printed. There will be a 
variable number of "A"s printed, zero or more, and exactly one "B", but the 
point is that in general BOTH will run, which is the opposite of if...else.

The actual semantics are:

- run the for block
- THEN unconditionally run the "else" block

The only way to skip running the "else" block is to jump out of the entire 
for...else statement, using `return`, `raise` or `break`.



> Here's how I make sense of for...else.  Consider this loop:
> 
> for item in seq:
>     if pred(item):
>         use(item)
>         break
> else:
>     not_found()

That's the major intended use of "else", but note that it is NOT paired with 
the `if`. You can have:

def func():
    for item in seq:
        if pred(item):
            use(item)
            if condition:
                return something
        else:
            different_use(item)
            continue
        do_more()
        break
    else:
        not_found()


or any of an infinite number of other combinations. You can't really understand 
for...else correctly if you think of the "else" being partnered with an "if" 
inside the loop. What if there is no "if"? Or ten of them? Of if they all 
already have "else" clauses?



> This particular loop functions as a kind of a dynamic if...elif...else
> statement.  You can see that if you unroll the loop:
> 
> 
> if pred(seq[0]):
>     use(seq[0])
> elif pred(seq[1]):
>     use(seq[1])
> elif pred(seq[2]):
>     use(seq[2])
> else:
>     not_found()

They are only equivalent because of the "break". Take the break out, unroll the 
loop, and what you have is:

if pred(seq[0]):
    use(seq[0])
if pred(seq[1]):
    use(seq[1])
if pred(seq[2]):
    use(seq[2])
not_found()

Put the break back in, and you have:


if pred(seq[0]):
    use(seq[0])
    GOTO foo  # not actual Python syntax, but see below
if pred(seq[1]):
    use(seq[1])
    GOTO foo
if pred(seq[2]):
    use(seq[2])
    GOTO foo
not_found()
label: foo

Admittedly GOTO isn't Python syntax, but this actually is the way that the 
bytecode is done: the else clause is executed unconditionally, and a break 
jumps past the else clause.

In Python 3.3, "break" is compiled to a JUMP_ABSOLUTE bytecode. You can see 
this for yourself using the dis module, e.g.:


import dis
code = compile("""
for i in seq:
    this()
    if condition:
        break
    that()
else:
    another()
print("done")
""", "", "exec")
dis.dis(code)



> You will note that the else block is the same in both the rolled and unrolled
> versions, and has exactly the same meaning and usage.

But not in the general case. Only certain specific uses of for...else behave as 
you suggest.


> As for a more appropriate keyword, I don't like the examples I saw skimming
> this thread; neither "finally" nor "then" communicates that the block would
> executed conditionally.

The block isn't executed conditionally. The block is executed UNCONDITIONALLY. 
The only way to avoid executing the "else" block is to jump past it, using a 
return, raise of break.


> If you want my opinion, you might as well use something explicit and
> unambiguous, like "if_exhausted", for the block; 

But that is exactly what the else clause is NOT. That is an incredibly common 
mistake that people make, thinking that the "else" clause executes when the 
sequence is exhausted. At first, it *seems* to be the case:

py> seq = []
py> for item in seq:
...     print("not empty!")
... else:
...     print("empty")
... 
empty

but that wrong. 

py> seq = [1, 2]
py> for item in seq:
...     print("not empty!")
... else:
...     print("empty")
... 
not empty!
not empty!
empty
py> print(seq)  # not exhausted
[1, 2]


The else clause has nothing to do with whether or not the for block runs, or 
whether it is empty, or whether the iterable is exhausted after the loop is 
complete. The else clause simple runs directly after the for block, unless you 
skip it by using the Python equivalent of a GOTO.


> If you really want my opinion, it probably shouldn't be in the language at
> all, even though I happily use it from time to time, and my code is better
> for it.

o_O



> But it's not useful enough that the language would really suffer
> without it, and it would save some users from something that can be quite
> confusing.

As confusing as threads? As confusing as multiple inheritance? As confusing as 
asynchronous programming?

If you have seen as many beginners ask "why do I always get 100 heads or 100 
tails?" as I have:


count_heads = 0
coin = random.choose(['heads', 'tails'])
for i in range(100):
    if coin == 'heads':
        count_heads += 1

print("number of heads:", count_heads)
print("number of tails:", 100 - count_heads)


then you will understand that "confusing" is often a matter of experience and 
understanding. Once you have the correct understanding of "for...else", there 
is nothing confusing about it.


-- 
Steve




More information about the Python-list mailing list