192

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.

alexis
  • 48,685
  • 16
  • 101
  • 161
  • 6
    @alexis The edit by Dharman was made in an attempt to make your question not opinion based while maintaining the question itself, IMO the edit makes the post an awful lot better (and not worthy of closure) – Nick is tired Nov 17 '20 at 12:01
  • @Dharman, I appreciate your efforts but your edits completely distort the intent and content of the question. Please stop. – alexis Nov 17 '20 at 12:07
  • 3
    *"how can I think about the `else` keyword so that its semantics make sense, and I can therefore remember it?"* - Explaining to you specifically how we can help you personally remember how `else` works isn't a helpful question, Dharman is the only reason I retracted a close vote on the question, because without it the question is opinion based – Nick is tired Nov 17 '20 at 12:07
  • 1
    Thank you for the explanation, @nick. Nevertheless, Dharman's title makes it a duplicate of a very different question. If you are convinced that this is too opinion-based, i can leave with your vote. But please leave the question alone. – alexis Nov 17 '20 at 12:09
  • 1
    Also the question is about **making sense** of this design, not about what it does. – alexis Nov 17 '20 at 12:14

15 Answers15

218

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
drawoc
  • 1,586
  • 1
  • 9
  • 11
  • 8
    This is a most excellent answer. Treat your loops like a series of elif statements and the else behaviour will expose its natural logic. – Nomenator Jun 07 '16 at 16:31
  • 1
    I also like this answer, but it is not drawing an analogy with a series of `elif` statements. There's [an answer](http://stackoverflow.com/a/37643965/699305) that does, and it has one net upvote. – alexis Jun 08 '16 at 08:15
  • 3
    well not exactly, a while loop could have the condition meet False right before it `break`s, in which case the `else` would not run but the condition is False. Similarly with `for` loops it can `break` on the last element. – Tadhg McDonald-Jensen Jun 15 '16 at 15:16
36

Better to think of it this way: The else block will always be executed if everything goes right in the preceding for block such that it reaches exhaustion.

Right in this context will mean no exception, no break, no return. Any statement that hijacks control from for will cause the else block to be bypassed.


A common use case is found when searching for an item in an iterable, for which the search is either called off when the item is found or a "not found" flag is raised/printed via the following else block:

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continue does not hijack control from for, so control will proceed to the else after the for is exhausted.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • 22
    Sounds very nice... but then you'd expect an `else` clause to be executed when things *don't* go right, wouldn't you? I'm already getting confused again... – alexis Jun 05 '16 at 15:43
  • I have to disagree with you on "Technically, It isn't [semantically similar to every other `else`]", since the `else` is run when none of the conditions in the for loop evaluate to True, as I demonstrate in [my answer](http://stackoverflow.com/a/37643965/5827215) – Tadhg McDonald-Jensen Jun 05 '16 at 16:43
  • @TadhgMcDonald-Jensen You can also break the loop on a `False`. So the question of how the `for` is *broken* depends on the use case. – Moses Koledoye Jun 05 '16 at 16:54
  • That's right, I am asking for a way to somehow relate what happens to the English meaning of "else" (which is indeed reflected in other uses of `else` in python). You provide a good intuitive summary of what `else` does, @Moses, but not of how we could associate this behavior with "else". If a different keyword was used (e.g., `nobreak` as mentioned in [this answer](http://stackoverflow.com/a/23748240/699305) to a related question), it would be easier to make sense of. – alexis Jun 05 '16 at 16:58
  • @alexis From the link you added, given the etymology of this `else`, I think we'll have to accept the status quo :) – Moses Koledoye Jun 05 '16 at 17:38
  • What status quo do you mean? I certainly wasn't aiming for a change in Python's syntax! – alexis Jun 05 '16 at 17:40
  • That this `else` does not behave the way it sounds. And to understand it, you'll have to apply some abstraction like the `nobreak` everyone already proposed. – Moses Koledoye Jun 05 '16 at 17:47
  • 1
    It really has nothing to do with "things going right". The else is purely executed when the `if`/`while` condition evaluates to false or `for` is out of items. `break` exists the containing loop (after the `else`). `continue` goes back and evaluates the loop condition again. – Mark Tolonen Jun 05 '16 at 23:40
  • @MosesKoledoye ok so in a chain of `if/elif`s the only way to `break` out of the chain is for one of the conditionals to be `True`, where as a for loop is exited with the explicit `break` (and both are broken by `return` or exception) I certainly don't think of things going "wrong" when the for loop needs to break. – Tadhg McDonald-Jensen Jun 05 '16 at 23:48
  • *Going right* is an oversimplification, and I already described what I meant by that in the answer. – Moses Koledoye Jun 06 '16 at 07:42
33

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 out 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.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • 1
    What you say sounds very sensible, but lumping the three termination conditions together, "until [the condition] is False or breaks/continues", is wrong: Crucially, the `else` clause is executed if the loop is exited with `continue` (or normally), but **not** if we exit with `break`. These subtleties are why I'm trying to really grok what `else` catches and what it does not. – alexis Jun 05 '16 at 15:31
  • 4
    @alexis yes I needed to clarify there. Edited. continue doesn't execute the else, but does return to the top of the loop which may then evaluate to false. – Mark Tolonen Jun 05 '16 at 22:11
24

This is what it essentially means:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

It's a nicer way of writing of this common pattern:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

The else clause will not be executed if there is a return because return leaves the function, as it is meant to. The only exception to that which you may be thinking of is finally, whose purpose is to be sure that it is always executed.

continue has nothing special to do with this matter. It causes the current iteration of the loop to end which may happen to end the entire loop, and clearly in that case the loop wasn't ended by a break.

try/else is similar:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
Alex Hall
  • 34,833
  • 5
  • 57
  • 89
20

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.

Keiwan
  • 8,031
  • 5
  • 36
  • 49
  • I pretty much like this answer, but you can simplify: Omit the `end` label and just put the `goto loop` inside the `if` body. Maybe even outdent by putting the `if` on the same line as the label, and it suddenly looks very much like the orignal. – Bergi Jun 07 '16 at 01:32
  • @Bergi Yes, I think that makes it a bit clearer, thanks. – Keiwan Jun 07 '16 at 08:21
16

My gotcha moment with the loop's else clause was when I was watching a talk by Raymond Hettinger, who told a story about how he thought it should have been called nobreak. Take a look at the following code, what do you think it would do?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

What would you guess it does? Well, the part that says nobreak would only be executed if a break statement wasn't hit in the loop.

Community
  • 1
  • 1
Nasser Al-Shawwa
  • 3,573
  • 17
  • 27
8

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.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
7

Others have already explained the mechanics of while/for...else, and the Python 3 language reference has the authoritative definition (see while and for), but here is my personal mnemonic, FWIW. I guess the key for me has been to break this down into two parts: one for understanding the meaning of the else in relation to the loop conditional, and one for understanding loop control.

I find it's easiest to start by understanding while...else:

while you have more items, do stuff, else if you run out, do this

The for...else mnemonic is basically the same:

for every item, do stuff, but else if you run out, do this

In both cases, the else part is only reached once there are no more items to process, and the last item has been processed in a regular manner (i.e. no break or return). A continue just goes back and sees if there are any more items. My mnemonic for these rules applies to both while and for:

when breaking or returning, there's nothing else to do,
and when I say continue, that's "loop back to start" for you

– with "loop back to start" meaning, obviously, the start of the loop where we check whether there are any more items in the iterable, so as far as the else is concerned, continue really plays no role at all.

Fabian Fagerholm
  • 4,099
  • 1
  • 35
  • 45
  • 4
    I 'd suggest that it could be enhanced by saying that the usual purpose of a for/else loop is to examine items *until you've found what you're looking for and want to stop*, or you run out of items. The "else" exists to handle the "you run out of items (without having found what you were looking for)" part. – supercat Jun 06 '16 at 16:14
  • @supercat: Could be, but I don't know what the most common uses are out there. The `else` could also be used to do something when you're simply finished with all the items. Examples include writing a log entry, updating a user interface, or signaling some other process that you're done. Anything, really. Also, some pieces of code has the "successful" case end with a `break` inside the loop, and the `else` is used to handle the "error" case where you didn't find any suitable item during the iteration (maybe that was what you were thinking of?). – Fabian Fagerholm Jun 06 '16 at 17:47
  • 1
    The case I was thinking of was *precisely* the case where the successful case ends with a "break", and the "else" handles a lack of success. If there's no "break" within a loop, the "else" code may as well simply follow the loop as part of the enclosing block. – supercat Jun 06 '16 at 17:57
  • Unless you need to distinguish between the case where the loop went through all the iterable items without interruption (and that was a successful case) and the case where it didn't. Then you have to put the "finalising" code in the loop's `else` block, or keep track of the result using other means. I basically agree, I'm just saying I don't know how people use this feature and therefore I'd like to avoid making assumptions of whether the "`else` handles successful case" scenario or the "`else` handles unsuccessful case" scenario is more common. But you have a good point, so comment upvoted! – Fabian Fagerholm Jun 06 '16 at 18:27
7

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
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
6

The way I see it, else: fires when you iterate past the end of the loop.

If you break or return or raise you don't iterate past the end of loop, you stop immeadiately, and thus the else: block won't run. If you continue you still iterate past the end of loop, since continue just skips to the next iteration. It doesn't stop the loop.

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
  • 1
    I like that, I think you're on to something. It ties in a little with how looping used to be implemented in the bad old days before loop keywords. (Namely: the check was placed at the *bottom* of the loop, with a `goto` the top on success.) But it's a shorter version of the top-voted answer... – alexis Jun 07 '16 at 18:30
  • @alexis, subjective, but I find my way of expressing it easier to think about. – Winston Ewert Jun 07 '16 at 20:31
  • actually I agree. If only because it's pithier. – alexis Jun 07 '16 at 21:09
4

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)

  • 1
    Where is the `else`? If you meant the `done:` label to stand proxy or `else:`, I believe you have it exactly backwards. – alexis Jun 06 '16 at 13:20
  • @alexis The 'else' code would fill in the '...' *immediately before* the `done:` label. The overall correspondence is, maybe, best said thus: Python has the `else`-on-loop construct so that you can express this control flow pattern without `goto`. – zwol Jun 06 '16 at 16:00
  • There are other ways to execute this control flow pattern, e.g. by setting a flag. That's what the `else` avoids. – alexis Jun 06 '16 at 18:17
2

If you pair else with for, it could be confusing. I don't think the keyword else was a great choice for this syntax, but if you pair else with if which contains break, you can see it actually makes sense. else is barely useful if there is no preceding if statement and I believe this is why the syntax designer chose the keyword.

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.

1

The way I think about it, the key is to consider the meaning of continue rather than else.

The other keywords you mention break out of the loop (exit abnormally) whilst continue does not, it just skips the remainder of the code block inside the loop. The fact that it can precede loop termination is incidental: the termination is actually done in the normal way by evaluation of the loop conditional expression.

Then you just need to remember that the else clause is executed after normal loop termination.

Bob Sammers
  • 3,080
  • 27
  • 33
0

The while statement with an else clause

while condition:
    iteration
else:
    conclusion

is exactly equivalent to

while True:
    if not condition:
        conclusion
        break
    iteration

The for statement with an else clause

for item in iterable:
    iteration
else:
    conclusion

is exactly equivalent to

iterator = iter(iterable)
while True:
    try:
        item = next(iterator)
    except StopIteration:
        conclusion
        break
    iteration

It helps understand the effect of a break or continue statement in the iteration statement.

Note. — For the while and for statements without an else clause, replace the conclusion statement with a pass statement in the equivalent code.

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
  • 1
    A subtlety of `for` loop is it only catches `StopIteration` raised while executing `next(iterator)`. If something else inside `iteration` block raised `StopIteration`, that exception would blow out of the loop (and would not execute `conclusion`). But it's messy to write that accurately (without resorting to while..else). – Beni Cherniavsky-Paskin Mar 24 '23 at 15:22
  • 1
    @BeniCherniavsky-Paskin Thank you very much, I have fixed the code (hopefully). It’s actually easier to read now. – Géry Ogam Mar 24 '23 at 16:51
-1
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
Down the Stream
  • 639
  • 7
  • 10