"for/while ... break(by any means) ... else" make sense?

Steven D'Aprano steve at pearwood.info
Wed Jun 29 20:27:15 EDT 2016

On Wed, 29 Jun 2016 08:01 pm, Victor Savu wrote:

> There are many posts trying to explain the else after for or while. Here
> is my take on it:
> There are three ways of getting out of a (for/while) loop: throw, break or
> the iterator gets exhausted.

- reaching the end of the loop
- raise (not throw)
- return (if inside a function)
- calling os.abort()
- calling os._exit()
- break

So at least six ways.

> The question is, how cab we tell which way we exited?

I'm not really sure that is the right question to ask, but okay, let's
continue and see where this goes.

> For the throw, we have the except clause. 

Which `except` clause?

`except` is not part of the for statement, it is completely independent.
There may, or may not, be an `except` clause anywhere in your code.

In either case, the `raise` behaves like a GOTO, jumping to the nearest
`except` clause (if any). That may be inside the loop:

for x in seq:
    except Error:

or outside the loop:

    for x in seq:
except Error:

or there may be no except clause at all, and control is transferred to the
top-level error handler (if any) or to the Python interpreter, which then
prints a stack trace and exits.

> This leaves us to  
> differentiatr between break and normal exhaustion of the iterator.

Following `return`, the function returns and transfer returns to the
caller's code.

Following os.abort(), the interpreter exits in the hardest, quickest manner

Following os._exit(), the interpreter exits without doing any normal cleanup
or processing.

In those two cases, we cannot run any Python code after the function is
called, so the question of distinguishing them from ending the loop
normally doesn't come up. Nevertheless, they do exit the loop.

> This is 
> that the else clause is for: we enter the body iff the loop iterator was
> exhausted.

The way I would put it is that we enter the body of the `else` statement
when the loop reaches the end. That applies to both `while` and `for`
loops, and it applies to looping over sequences using the sequence protocol
instead of the iterator protocol. It applies to empty sequences (the loop
reaches the end immediately). And it even applies to infinite loops: if the
loop never ends, the `else` statement never runs.

And most importantly, if we transfer control out of the `for` statement
using *any* other mechanism (break, return, raise, os.abort, ...) then the
`else` statement never runs because we have jumped past it.

> I for one would have chosen 'then' as a keyword to mark
> something that naturally happens as part of the for statement but after
> the looping is over; 

I agree with this.

> assuming break jumps out of the entire statement, it 
> makes sense that it skips the 'then' body as well.

And this.

> (In the same way, I 
> prefer 'catch' to 'except' as a correspondent to 'throw', 

There is no "throw" in Python, there is "raise".

> but all of this 
> is just bikeshedding). At a language design level, the decision was made
> to reuse one of the existing keywords and for better or worse, 'else' was
> chosen, which can be thought of as having no relation to the other use of
> the same keyword in the 'if' statement. The only rationale behind this was
> to save one keyword.


“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