Python – best practices using else block

Typical use case of else block is with if in many programming languages.
It usually serves as branch control such if it doesn’t meet certain conditions in if block it will just execute else block instead of if block.
And it is the same case for python else block too.

However, python extended else block usage more than the above.
In python, you can actually use else block not only with if but also with for, while, try blocks.
In that case, the name – else – could be quite misleading when used with for, while, try blocks.
Since we automatically think of if-else we might think it will execute some code inside else block if for, while, try fails.
But it is not the case.

Using else block with for, while, try blocks means to execute code inside else block after executing for, while, try blocks.
In that case, I personally think the name ‘then‘ is more appropriate but for some reason else is used there.
For that reason, I think it is generally recommended not to use else block after for, while loops to avoid any miscommunication.

Regardless, I will still explain else block for each case with examples.

for/else

The else block will run only if for loop completes entirely.
It means that else block will not run for some cases. (i.e. for loop exits with break statement)

Code Example

for i in range(5):
    print(f"loop value:{i}")
else:
    print("else block")
    
# output
# loop value:0
# loop value:1
# loop value:2
# loop value:3
# loop value:4
# else block

for i in range(5):
    print(f"loop value:{i}")
    if i == 3:
        break
else:
    print("else block")
    
# output
# loop value:0
# loop value:1
# loop value:2
# loop value:3

Again, I personally think this is pretty confusing and do not recommend to use it.

while/else

The else block will run only if while loop exits because its condition became false.
Just like for loop, if while loop exits because of break, else block will not run.

counter = 0

while counter < 2:
    counter += 1
    print(f"counter: {counter}");
else:
    print("else block")
    
# output
# counter: 1
# counter: 2
# else block

counter = 0

while counter < 2:
    counter += 1
    print(f"counter: {counter}");
    if counter == 1:
        break
else:
    print("else block")

# output
# counter: 1

While loop is basically the same as for loops and I do not recommend to use while/else together because it looks counter-intuitive.

try/except/else/finally

As you may already know EAFP – easier to ask for forgiveness than permission – is typically considered as more pythonic.
Because of that reason try/except is actually pretty commonly used not only for error handling but also for flow control in python.

At first glance, it seems to be redundant to put else block after try/except.
However, it actually does add some correctness, readability, and clarity.

Let’s take a look at the example below.

try:
    call_may_raise_exception()
    process_no_exception()
    release_resource()
except IndexError:
    print("exception happened")

This python code calls three functions: call_may_raise_exception(), process_no_exception(), and release_resource().
The first function does something and may raise an exception.
The second function just processes some data and does not raise any exception.
The third function releases some resources after all the work.

Now, we probably want to call release_resource no matter what to avoid any leaks.
So it makes sense to put release_resource() function call under finally block like this.

try:
    call_may_raise_exception()
    process_no_exception()
except IndexError:
    print("exception happened")
finally:
    release_resource()

But what about process_no_exception() function?
Strictly speaking, process_no_exception() function should not be placed inside try block since it’s not going to raise any exceptions.
But at the same time, we want to call it before release_resource() only if there weren’t any exceptions.
That’s where we can use else block.

try:
    call_may_raise_exception()
except IndexError:
    print("exception happened")
else:
    process_no_exception()
finally:
    release_resource()

It’s not only correct but also more readable.
The else block makes it clear call_may_raise_exception() function needs to be guarded against and process_no_exception() will be called only if there weren’t any exceptions raised from call_may_raise_exception() function.
In the end, release_resource() is called no matter what.

The else block with try/except/finally is actually recommended since it adds correctness, readability, and clarity.

Conclusion

Python supports else block after for, while loops and try/except.
However, it is not recommended to use else with for/while loops.
Although it seems redundant to use else with try/except the else block actually adds some benefits and is recommended to use with them.