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