How can I make sense of the `else` clause of Python loops?
Question:
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.
Answers:
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.
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:
...
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.
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.
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 elif
s, 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.
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.
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)
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 break
ing or return
ing, 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.
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.
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("verynlongnword"));
}
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
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.
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.
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.
# 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']
'''
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.
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 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.
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:
...
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.
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.
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 elif
s, 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.
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.
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)
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, butelse
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
break
ing orreturn
ing, there’s nothingelse
to do,
and when I saycontinue
, 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.
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.
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("verynlongnword"));
}
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 theelse
path by doing less in theif
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
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.
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.
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 suspectsif
anyone is the criminal
break
the investigation.else
report failure.
# 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']
'''
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.