for / while else doesn't make sense

Steven D'Aprano steve at pearwood.info
Sat May 21 06:39:24 EDT 2016


On Sat, 21 May 2016 05:20 pm, Erik wrote:

> On 20/05/16 01:06, Steven D'Aprano wrote:
> 
>> In my experience, some people (including me) misunderstand "for...else"
>> to mean that the else block runs if the for block *doesn't*. It took me
>> the longest time to understand why this didn't work as I expected:
>>
>> for x in seq:
>>      pass
>> else:
>>      print("seq is empty")
> 
> So why don't we consider if that should be a syntax error - "else"
> clause on "for" loop with no "break" in its body? I know that doesn't
> change your fundamental mental model, but it's a hint that it's wrong :)

Just for the record, that's not my mental model *now*.

It took me a long time to work out what for...else was actually doing, but
some years ago I finally managed to do so. It's my argument that had the
keyword been "then" (implying that the `then` block is unconditionally
executed after the `for` block, which is the actual behaviour of the
interpreter) rather than "else" implying that it is an alternative to the
`for` block) I wouldn't have come up with the wrong mental model in the
first place. And I'm not the only one who has come up with the same, wrong,
model.

But you do ask a good question. Why isn't for...else with no break a syntax
error? I suppose it could be. But that's a *stylistic* question, and Python
generally treats that as "none of the compiler's business". It's not the
business of the compiler to enforce good code, only legal code. We can
legally write lots of code of questionable value:

while 0:
    print("surprise!")

n = 1 + int(x) - 1

x = x

try:
    something()
finally:
    pass


without the compiler judging us and making it a syntax error. Why should
for...else be any different? The behaviour is perfectly well defined:

- first the `for` block runs, as many times as needed;
- then the `else` block runs, once.


Of course, the usual four keywords (continue, break, return, raise) will
change the order of execution by jumping to:

- the top of the for loop;
- past the for...else blocks;
- out of the current function;
- the nearest exception handler

respectively. But in the absence of a jump, for...else works as specified,
regardless of whether there is a break in it or not.


> As it's backwards-incompatible, it could be introduced using a
> __future__ import (a precedent is 'generators' and the "yield" keyword
> back in the day) which those who would like the new check could add to
> the top of their sources.

Sure, we *could* do this, but as I said, it does go against the usual
philosophy that the compiler shouldn't make judgements on what is good code
and what isn't. 


> But also, as you say, any instances of that construct in the wild is
> almost certainly a bug, 

I don't think it was me calling it a bug.


> so it would be good to be able to test code
> using a command-line or similar switch to turn on the behaviour by
> default for testing existing bodies of code.

I think that switch is called "use Pylint, PyChecker, Jedi or some other
opinionated linter or style-checker".

 
> I notice that pylint complains about this (as a warning). Is there any
> reason why this should _not_ just be considered an error and be done
> with it?

Because there's nothing actually broken with it.

There's lots of code which a human programmer would recognise as "silly",
or "wrong", but is well-defined and legal. Just because you can't think of
a reason to do something doesn't mean it should be prohibited.



-- 
Steven




More information about the Python-list mailing list