Why does python use 'else' after for and while loops?
I understand how this construct works:
for i in range(10):
print(i)
if i == 9:
print("Too big - I'm giving up!")
break
else:
print("Completed successfully")
But I don't understand why else
is used as the keyword here, since it suggests the code in question only runs if the for
block does not complete, which is the opposite of what it does! No matter how I think about it, my brain can't progress seamlessly from the for
statement to the else
block. To me, continue
or continuewith
would make more sense (and I'm trying to train myself to read it as such).
I'm wondering how Python coders read this construct in their head (or aloud, if you like). Perhaps I'm missing something that would make such code blocks more easily decipherable?
Solution 1:
A common construct is to run a loop until something is found and then to break out of the loop. The problem is that if I break out of the loop or the loop ends I need to determine which case happened. One method is to create a flag or store variable that will let me do a second test to see how the loop was exited.
For example assume that I need to search through a list and process each item until a flag item is found and then stop processing. If the flag item is missing then an exception needs to be raised.
Using the Python for
...else
construct you have
for i in mylist:
if i == theflag:
break
process(i)
else:
raise ValueError("List argument missing terminal flag.")
Compare this to a method that does not use this syntactic sugar:
flagfound = False
for i in mylist:
if i == theflag:
flagfound = True
break
process(i)
if not flagfound:
raise ValueError("List argument missing terminal flag.")
In the first case the raise
is bound tightly to the for loop it works with. In the second the binding is not as strong and errors may be introduced during maintenance.
Solution 2:
It's a strange construct even to seasoned Python coders. When used in conjunction with for-loops it basically means "find some item in the iterable, else if none was found do ...". As in:
found_obj = None
for obj in objects:
if obj.key == search_key:
found_obj = obj
break
else:
print('No object found.')
But anytime you see this construct, a better alternative is to either encapsulate the search in a function:
def find_obj(search_key):
for obj in objects:
if obj.key == search_key:
return obj
Or use a list comprehension:
matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
print('Found {}'.format(matching_objs[0]))
else:
print('No object found.')
It is not semantically equivalent to the other two versions, but works good enough in non-performance critical code where it doesn't matter whether you iterate the whole list or not. Others may disagree, but I personally would avoid ever using the for-else or while-else blocks in production code.
See also [Python-ideas] Summary of for...else threads
Solution 3:
There's an excellent presentation by Raymond Hettinger, titled Transforming Code into Beautiful, Idiomatic Python, in which he briefly addresses the history of the for ... else
construct. The relevant section is "Distinguishing multiple exit points in loops" starting at 15:50 and continuing for about three minutes. Here are the high points:
- The
for ... else
construct was devised by Donald Knuth as a replacement for certainGOTO
use cases; - Reusing the
else
keyword made sense because "it's what Knuth used, and people knew, at that time, all [for
statements] had embedded anif
andGOTO
underneath, and they expected theelse
;" - In hindsight, it should have been called "no break" (or possibly "nobreak"), and then it wouldn't be confusing.*
So, if the question is, "Why don't they change this keyword?" then Cat Plus Plus probably gave the most accurate answer – at this point, it would be too destructive to existing code to be practical. But if the question you're really asking is why else
was reused in the first place, well, apparently it seemed like a good idea at the time.
Personally, I like the compromise of commenting # no break
in-line wherever the else
could be mistaken, at a glance, as belonging inside the loop. It's reasonably clear and concise. This option gets a brief mention in the summary that Bjorn linked at the end of his answer:
For completeness, I should mention that with a slight change in syntax, programmers who want this syntax can have it right now:
for item in sequence: process(item) else: # no break suite
* Bonus quote from that part of the video: "Just like if we called lambda makefunction, nobody would ask, 'What does lambda do?'"
Solution 4:
To make it simple, you can think of it like that;
- If it encounters the
break
command in thefor
loop, theelse
part will not be called. - If it does not encounter the
break
command in thefor
loop, theelse
part will be called.
In other words, if for loop iteration is not "broken" with break
, the else
part will be called.