python python, - How can I make sense of the `else` clause of Python loops?




7 Answers

(This is inspired by @Mark Tolonen's answer.)

An if statement runs its else clause if its condition evaluates to false. Identically, a while loop runs the else clause if its condition evaluates to false.

This rule matches the behavior you described:

  • In normal execution, the while loop repeatedly runs until the condition evaluates to false, and therefore naturally exiting the loop runs the else clause.
  • When you execute a break statement, you exit out of the loop without evaluating the condition, so the condition cannot evaluate to false and you never run the else clause.
  • When you execute a continue statement, you evaluate the condition again, and do exactly what you normally would at the beginning of a loop iteration. So, if the condition is true, you keep looping, but if it is false you run the else clause.
  • Other methods of exiting the loop, such as return, do not evaluate the condition and therefore do not run the else clause.

for loops behave the same way. Just consider the condition as true if the iterator has more elements, or false otherwise.

list)? statement?

Many Python programmers are probably unaware that the syntax of while loops and for loops includes an optional else: clause:

for val in iterable:
    do_something(val)
else:
    clean_up()

The body of the else clause is a good place for certain kinds of clean-up actions, and is executed on normal termination of the loop: I.e., exiting the loop with return or break skips the else clause; exiting after a continue executes it. I know this only because I just looked it up (yet again), because I can never remember when the else clause is executed.

Always? On "failure" of the loop, as the name suggests? On regular termination? Even if the loop is exited with return? I can never be entirely sure without looking it up.

I blame my persisting uncertainty on the choice of keyword: I find else incredibly unmnemonic for this semantics. My question is not "why is this keyword used for this purpose" (which I would probably vote to close, though only after reading the answers and comments), but how can I think about the else keyword so that its semantics make sense, and I can therefore remember it?

I'm sure there was a fair amount of discussion about this, and I can imagine that the choice was made for consistency with the try statement's else: clause (which I also have to look up), and with the goal of not adding to the list of Python's reserved words. Perhaps the reasons for choosing else will clarify its function and make it more memorable, but I'm after connecting name to function, not after historical explanation per se.

The answers to this question, which my question was briefly closed as a duplicate of, contain a lot of interesting back story. My question has a different focus (how to connect the specific semantics of else with the keyword choice), but I feel there should be a link to this question somewhere.




When does an if execute an else? When its condition is false. It is exactly the same for the while/else. So you can think of while/else as just an if that keeps running its true condition until it evaluates false. A break doesn't change that. It just jumps of of the containing loop with no evaluation. The else is only executed if evaluating the if/while condition is false.

The for is similar, except its false condition is exhausting its iterator.

continue and break don't execute else. That isn't their function. The break exits the containing loop. The continue goes back to the top of the containing loop, where the loop condition is evaluated. It is the act of evaluating if/while to false (or for has no more items) that executes else and no other way.




If you think of your loops as a structure similar to this (somewhat pseudo-code):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

it might make a little bit more sense. A loop is essentially just an if statement that is repeated until the condition is false. And this is the important point. The loop checks its condition and sees that it's false, thus executes the else (just like a normal if/else) and then the loop is done.

So notice that the else only get's executed when the condition is checked. That means that if you exit the body of the loop in the middle of execution with for example a return or a break, since the condition is not checked again, the else case won't be executed.

A continue on the other hand stops the current execution and then jumps back to check the condition of the loop again, which is why the else can be reached in this scenario.




Usually I tend to think of a loop structure like this:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

To be a lot like a variable number of if/elif statements:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

In this case the else statement on the for loop works exactly like the else statement on the chain of elifs, it only executes if none of the conditions before it evaluate to True. (or break execution with return or an exception) If my loop does not fit this specification usually I choose to opt out of using for: else for the exact reason you posted this question: it is non-intuitive.




In Test-driven development (TDD), when using the Transformation Priority Premise paradigm, you treat loops as a generalization of conditional statements.

This approach combines well with this syntax, if you consider only simple if/else (no elif) statements:

if cond:
    # 1
else:
    # 2

generalizes to:

while cond:  # <-- generalization
    # 1
else:
    # 2

nicely.

In other languages, TDD steps from a single case to cases with collections require more refactoring.


Here is an example from 8thlight blog:

In the linked article at 8thlight blog, the Word Wrap kata is considered: adding line breaks to strings (the s variable in the snippets below) to make them fit a given width (the length variable in the snippets below). At one point the implementation looks as follows (Java):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

and the next test, that currently fails is:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

So we have code that works conditionally: when a particular condition is met, a line break is added. We want to improve the code to handle multiple line breaks. The solution presented in the article proposes to apply the (if->while) transformation, however the author makes a comment that:

While loops can’t have else clauses, so we need to eliminate the else path by doing less in the if path. Again, this is a refactoring.

which forces to do more changes to the code in the context of one failing test:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

In TDD we want to write as less code as possible to make tests pass. Thanks to Python's syntax the following transformation is possible:

from:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

to:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s



Think of the else clause as being part of the loop construct; break breaks out of the loop construct entirely, and thus skips the else clause.

But really, my mental mapping is simply that it's the 'structured' version of the pattern C/C++ pattern:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

So when I encounter for...else or write it myself, rather than understand it directly, I mentally translate it into the above understanding of the pattern and then work out which parts of the python syntax map to which parts of the pattern.

(I put 'structured' in scare quotes because the difference is not whether the code is structured or unstructured, but merely whether there are keywords and grammar dedicated to the particular structure)




If you try to pair else with for in your mind, it could be confusing. I don't think the keyword else was a great choice for this syntax, But if you pair else with break, you can see it actually makes sense.

Let me demonstrate it in human language.

for each person in a group of suspects if anyone is the criminal break the investigation. else report failure.


else is barely useful if there weren't break in the for loop anyway.




Related

python loops for-loop while-loop