How and When To Use For Else in Python Loop
Can you use else in a for loop in Python? The answer is a resounding yes! I know it sounds crazy. The use of else
after for
and while
loops in Python is not a widely known idiom. The use of the for-else construct is often misunderstood and overlooked.
For those who are coming from other programming languages, the syntax may be jarring. The fault is not on them though, the else
clause is mostly associated only with the if
statement.
But hold on, don’t jump to conclusions just yet! The else clause in loops may seem strange at first, but it can be a useful tool in your Python toolkit.
TL;DR
- Think of
else
in a for-else construct as “no break”. - Consider using
else
withfor
orwhile
loops instead of a flag variable to handle functionbreak
out of the loop. - An
else
clause is only useful with a precedingbreak
in a loop. In other words, it’s pointless to use the for-else construct without abreak
statement.
What is for else in Python
Even today, the use of the for-else construct in Python remains largely unpopular. It often confuses even seasoned Python programmers.
Here’s a trick to remember — the else
in a for-else construct can be remembered as “no break”.
It basically handles cases where no break
statement is being executed within a loop. Easy, right?
But, when would you use the for-else construct in Python?
When and how to use else in a for loop
A common use case is to go through a loop until something is found. When we find what we want, we would then break out of the loop and handle that case accordingly.
In short, we need to determine which case has happened:
- We found the target (
break
out early) - We finished the loop without finding the target
One common method is to create a flag variable that will let us do a check later to see how the loop was exited.
Now, let’s take a look at an example. Say you want to find the index of the first occurrence of a target in a sequence. You can do this using a flag variable:
Although this example is a little bit simplistic, code like this sometimes intermesh with other more complex code and there’s no shortcut out (i.e. can’t return
the result immediately).
However, using the for-else construct, we can instead have:
Perhaps looking more elegant, this can simplify your code and improve readability instead of using a flag variable with an additional if
check.
In short, the else
clause in a for
loop can be used to execute a block of code when the loop has finished running without encountering a break
statement.
For fun: benchmark
As I was down this rabbit hole, I got a bit curious about how these 4 different methods would perform. With some semantical differences, these functions can be used to find the index of the first occurrence of a character within a string.
Here’s what I ran:
Here are the results:
4 function calls in 6.462 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.019 0.019 6.462 6.462 <string>:1(<module>)
1 6.443 6.443 6.443 6.443 <string>:2(flag_var_find)
1 0.000 0.000 6.462 6.462 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
4 function calls in 5.973 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.008 0.008 5.973 5.973 <string>:1(<module>)
1 5.965 5.965 5.965 5.965 <string>:2(else_find)
1 0.000 0.000 5.973 5.973 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5 function calls in 5.763 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.007 0.007 5.763 5.763 <string>:1(<module>)
1 5.756 5.756 5.756 5.756 <string>:2(<genexpr>)
1 0.000 0.000 5.763 5.763 {built-in method builtins.exec}
1 0.000 0.000 5.756 5.756 {built-in method builtins.next}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
4 function calls in 0.012 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.007 0.007 0.012 0.012 <string>:1(<module>)
1 0.000 0.000 0.012 0.012 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.004 0.004 0.004 0.004 {method 'find' of 'str' objects}
Subtly, we can see that using the for-else construct is just a tiny bit faster than the flag variable way.
On the other hand, using Python’s built-in string find
method is significantly faster for this specific question. However, it will not work in the cases of other Sequence Types like Lists or Tuples.
Using else with while loops
Here’s a version of the same find
function using a while
loop instead of a for
loop:
def find(seq, target):
"""Find the index of the first occurrence of `target` in `seq`."""
i = 0
while i < len(seq):
if seq[i] == target:
return i
i += 1
else:
return -1
When not to use the else clause in a loop
Simple. It’s pointless to use a for-else construct without a preceding break
statement in a loop.
How to use else with a try-except block
Loops aside, the else
clause can also be used with a try-except block to run code when no exceptions were raised in the try block.
Similar to the for-else construct, the try-else construct is useful to distinguish between:
- The code ran successfully without any exception
- The code ran into an exception
try:
book = find_book(id=1)
except BookNotFoundError:
# handle the exception
print("Book not found! :(")
else:
# run this block if no exception was raised
send_slack_notification(book)
finally:
# this block is always executed
close_db_connection()
In this case, the code in the else
block will only run if the code in the try block was executed successfully, without any exceptions.
In summary, consider using the try-else construct to separate the normal execution path from the error handling code.
Do not confuse else
with finally
in try
blocks
- try-else — executes if there’s NO exception
- try-finally — always gets executed regardless of exception
with
statement) to encapsulate the use of try-finally instead when it comes to exception handling.What’s Next
To summarize, the else clause in Python provides a way to execute code after a loop has completed execution without encountering a break
statement.
I owe my thanks to this great talk by Raymond Hettinger, titled “Transforming Code into Beautiful, Idiomatic Python”. In the talk, he briefly spoke about the history of the for-else construct. I highly recommend watching it — I’ve been using Python for 5+ years and I still learn many great tidbits in such a short time from him.
I personally would use the for-else construct whenever it makes sense. Having that said, I would recommend trying to get your team to be on the same page before adopting this widely unpopular construct.
Lastly, if you’re interested to dive deeper into this rabbit hole, check out [Python-ideas] Summary of for...else threads.