for / while else doesn't make sense
Chris Angelico
rosuav at gmail.com
Sat May 21 03:29:20 EDT 2016
On Sat, May 21, 2016 at 5:20 PM, Erik <python at lucidity.plus.com> 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 :)
>
> 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.
>
> But also, as you say, any instances of that construct in the wild is almost
> certainly 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 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 it's not the language parser's job to decide what's "sensible"
and what's "not sensible". That's a linter's job. Some things
fundamentally can't work, but others work fine and might just be
not-very-useful - for example:
def f(x):
x if x <= 2 else x * f(x-1)
This is syntactically-legal Python code, but it probably isn't doing
what you want it to (in Python, a bare final expression does NOT
become the function's return value, although that does happen in other
languages). A linter should look at this and give a warning (even if
the else part has side effects, the first part can't, and the
condition shouldn't), but the language will happily evaluate it (this
example from CPython 3.6):
>>> dis.dis(f)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (2)
6 COMPARE_OP 1 (<=)
9 POP_JUMP_IF_FALSE 18
12 LOAD_FAST 0 (x)
15 JUMP_FORWARD 17 (to 35)
>> 18 LOAD_FAST 0 (x)
21 LOAD_GLOBAL 0 (f)
24 LOAD_FAST 0 (x)
27 LOAD_CONST 2 (1)
30 BINARY_SUBTRACT
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 BINARY_MULTIPLY
>> 35 POP_TOP
36 LOAD_CONST 0 (None)
39 RETURN_VALUE
So, yes, this function might load up the value of x (position 12),
then jump to 35 and discard that value, before unconditionally
returning None. Not a problem. It's up to the linter, and ONLY the
linter, to tell you about this.
ChrisA
More information about the Python-list
mailing list