replacing `else` with `then` in `for` and `try`

Steve D'Aprano steve+python at pearwood.info
Fri Nov 3 13:03:04 EDT 2017


On Fri, 3 Nov 2017 10:49 pm, Jon Ribbens wrote:

> On 2017-11-03, Steve D'Aprano <steve+python at pearwood.info> wrote:
>> On Fri, 3 Nov 2017 03:31 am, Jon Ribbens wrote:
>>> No, it's an obvious bug. You have a 'for...else' with no 'break'.
>>> Like I said, that should probably be a syntax error.
>>
>> It should absolutely not be a syntax error. There's no reason for it
>> to be a syntax error, except to satisfy some arrogant and foolish
>> idea of purity.
> 
> It'd be nice if you could be a little less rude. 

How else should we respond to the idea that something that has been legal
syntax for over twenty years and is in no way an error should be treated as a
syntax error, just to satisfy the opinion that nobody should write for...else
unless they are performing a search and it contains a break?


>> There are uncountable ways of writing code which is seemingly
>> "pointless", and we don't make it a syntax error.
> 
> And there's uncountable ways of writing code which we *do* make a
> syntax error. I'm not sure what your point is there.

None of these are syntax errors:

if False: do_something()

for x in '': pass

if True: pass

and uncountable other pieces of code which are, from some perspective,
pointless. We don't make them syntax errors because *pointless* doesn't imply
is is an error.

You think that a for...else loop with no break is pointless and "silly", even
when I demonstrate an actual use for it. Too bad. Even if it is "silly", in
your opinion, that still does not make it an error.

Is my point clear now?


>>> No, 'then' describes the opposite of what it does. The word 'then'
>>> implies something that always happens next,
>>
>> Right, which is what happens with the for...else block.
> 
> No.

Yes. It is not a matter of opinion that the `else` clause executes after
execution falls out the bottom of the loop, i.e. the loop runs, THEN the
`else` block runs.

It is an objective fact that in the absence of something which causes
execution to jump outside of the loop altogether (a return, a raise, or a
break) the execution order is loop first, then `else` block.

Both of these two sentences makes sense in English:

"Execution reaches the end of the loop, and THEN the `else` block runs."

"Execution reaches the end of the loop, or ELSE the `else` block runs."

but only the first describes what Python's for...else statement actually does.

This is not just my mental model, it is the actual objective behaviour of the
for...else statement.


> Ok, so look. It's obvious that you and I have different mental 
> models of the situation here. You're thinking of 'for...else' as two
> arbitrary clauses that run consecutively 

Scrub out the "arbitrary", and that is exactly what they are. They aren't
arbitrary: the `else` is optional, but if present it must follow after the
loop, and never the other way around.

The code inside the blocks can, of course, be any legal code we like. There's
no rule that says "only code approved by Jon Ribbens' is allowed", and
there's no rule that says "only searches are allowed".


> unless the whole thing is aborted by a 'break', 

Or return, raise, os._exit, os.abort, or having the process killed by the OS.
For simplicity we can ignore the last three (as well as more exotic
mechanisms such as "pull the power supply out" or "drop a two-tonne weight on
the computer"), but return and raise are common ways of exiting a loop and
should not be ignored or forgotten.


> whereas I'm thinking of the 'for' clause as 
> being a search for a situation that matches a condition and the
> 'else' clause being what happens if the condition is not matched
> (i.e. exactly the same as 'if...else').

That model conflates one specific use-case for the for...else statement with
the semantics of the statement, rather like insisting that "print displays a
greeting to the world" merely because 'print("Hello, World")' is so common.

The for loop does not necessarily perform a search:


count = 1
for obj in sequence:
    if count > MAX_OBJECTS:
        print("too many objects, halting")
        break
    process(obj)
else:
    print("finished")


According to your mental model, this code is... what? Illegal? Silly?
Impossible? A syntax error?

Obviously wrong and almost certainly buggy?

Even when there is a search, the sense of the search may be reversed from your
mental model:


for request in sequence:
    if 'please' not in request:
        break
    process(request)
else:
    print('done')
    return
raise SysExit('failure to say please')


It doesn't matter if you can think of alternative ways of writing these code
snippets. They're legal, and they work, and your mental model doesn't cover
them, at all or easily. That means your mental model is at best incomplete.

This is what happens when you treat one use-case for a general purpose
statement as synonymous with the statement itself. `for...else` is not just
for searching, and the `else` clause doesn't necessarily run if the search is
unsuccessful.

You've trapped yourself into considering the *motivating* use-case of
for...else as the *only* allowable use-case. Quoting you:


    if what the 'for' clause is doing doesn't match the concept 
    of 'searching for a match' then it's obvious that you shouldn't
    be using 'for...else'


Or to put it another way:

    If the meal you are preparing isn't a peanut butter and jelly
    sandwich, then it is obvious that you shouldn't be using bread
    in the first place.

which is no more or less foolish than your comment. for...else is not a
dedicated search function. It is a *control structure*, it controls the
execution order of code. We can use it for ANY purpose we like, not just
searching.


> Now there's nothing inherently *wrong* with your choice of mental
> model, except it's leading you into confusion 

What *did* lead me into confusion long ago was the keyword `else`. It took me
an embarrassingly long time to understand what for...else actually does,
because of the misleading keyword. It was only after I realised that the
`else` block runs after execution falls out the bottom of the loop that it
became clear.

In other words, it was only when I realised that `else` would be better
written as `then` that I understood the behaviour of the statement. If I read
it as "else", then the natural interpretation is that the block runs only if
the loop doesn't run:


for x in seq:
    pass
else:
    print("seq is empty")


In this very thread, we've seen experienced Pythonistas fall into that trap.


> because my model means the meaning of the 'else' keyword is intuitive
> and obvious, 

Your mental model cannot explain any use of for...else that doesn't involve a
search, or where the `else` block runs only on an unsuccessful search. I've
already given examples.

You'll probably dismiss them as "silly" because they don't meet your mental
model, and insist that I would be wrong to use them.

Your mental model cannot cope with the fact that for...else is a generic
control structure. It is not a dedicated "search" command. We can put
anything we like inside the blocks, even if it fails to match your mental
model. That just makes your model a poor match to reality.

Your response to code that doesn't match your mental model is to say that it
is obviously wrong and probably buggy and should be made into a syntax error
if possible. Rather than accept the limitations of your poor mental model,
you want to ban code that your model doesn't deal with.


> and yours  
> means it's counter-intuitive and confusing. Your suggestion is that
> the fix is to change the language, my suggestion is to fix your model.
> I'd suggest that changing your mind is easier than changing the
> language ;-)

And my mental model is to treat "else" in this concept as some foreign word,
perhaps Dutch, a false-friend that actually means "next" but due to some
awful coincidence happens to look exactly like the English word for "else".


> Also, my model has the advantage that if what the 'for' clause is
> doing doesn't match the concept of 'searching for a match' then it's
> obvious that you shouldn't be using 'for...else' in the first place.

Because legal, working code doesn't meet your mental model, it must be wrong.


> Again, sure, the language doesn't currently strictly enforce the
> requirement for a 'break', but nevertheless if you don't have one
> you almost certainly have a bug.

Ah yes, because it is inconceivable that anyone might have thought of a use
for for...else without a break. And if they have, well, it must be buggy.
Never mind if it works correctly or solves a problem, those things aren't
important. What matters is that only code that matches your model is allowed.


>>> Yes, I saw that. It's possible you are the only person in the world
>>> ever to have done that. It would not make the interactive interpreter
>>> 'worse' in the slightest for that silly trick to be lost.
>>
>> Just because you personally haven't used this technique doesn't make it
>> a "silly trick".
> 
> It's an incredibly obscure work-around for a different problem,

You mean a different problem to "searching"? Yes indeed it is.

Believe it or not, for...else is not limited to only solving problems
involving searching.

And as for "obscure", of course it would seem obscure to people who think that
the only use of for...else is for searching.


> i.e. an infelicity in the way the interactive prompt parses input
> blocks. If the parsing is a genuine issue then the answer is to
> fix that, not to look for hacks that almost never help anyway.

It helps solve my problem perfectly well, thank you. And you're welcome to
raise a bug report, but even if the REPL display issue is fixed in Python
3.7, I'll still be using this technique from time to time in older versions. 


>> And while I thank you for the complement that I am the cleverest and most
>> insightful Python coder in the world,
> 
> I didn't say anything even remotely resembling that.

That was sarcasm.



-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list