🤯TIL for...else exists in Python

Today, I learned that you can pair an else clause with a loop statement such as for or while. It looks like this:

for i in iterator:
    # do something
else:
    # do something else

This may look quite counterintuitive for the first time, but we will unwrap it in a moment! ✨

First, let's review the basic syntax of a for loop in Python. A for loop allows you to iterate over an iterator (such as a list, tuple, or string) and execute a block of code for each value. Here's an example:

fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)

This code would output:

apple
banana
cherry

Simple example

Now, let's add an else clause to this loop:

fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)
else:
    print("No more fruits")

This code would output:

apple
banana
cherry
No more fruits

In this case, the else block is executed after the for loop has finished normally. If the loop is exited early (for example, by a break statement), the else block will not be executed.

Linear search example

This for..else can be quite helpful in some scenarios. For example, you can use it to check if a value exists in a list:

values = [1, 2, 3, 4, 5]
for value in values:
    if value == 6:
        print("Value found!")
        break
else:
    print("Value not found.")

Let's take a look at another version without using for...else:

values = [1, 2, 3, 4, 5]

found = False
for value in values:
    if value == 6:
        found = True
        break

if found:
    print("Value found!")
else:
    print("Value not found.")

You can see, we have to introduce a boolean flag if we do not use the else clause. This makes the control flow more complex.

Breaking out of nested loops

In Python, the break statement only terminates the nearest enclosing loop. In some cases, you may want to break out of a parent loop.

Consider this code that determines if n is a prime number:

n = 11

if n <= 1:
    print(f"{n} is not a prime number!")
    exit()

for i in range(2, n):
    for j in range(2, n):
        if i * j == n:
            print(f"{n} is not a prime number!")
            break
    else:
        continue # only executed if the inner loop did NOT break
    break # only executed if the inner loop DID break
else:
    print(f"{n} is a prime number!")

It works by iterating over all pairs of numbers (i, j) less than n and checking if the product of any of them is equal to n. In the case where we find i * j is equal to n, we can say that n is not a prime number and we would want to exit the outer for loop early.

If the inner loop completes successfully (i.e., without breaking), it means that no pairs of numbers were found whose product equals n. In this case, the else clause of the inner loop is executed. This else clause triggers the continue statement, which causes the outer loop to immediately proceed to the next value of i without executing the remaining code in the outer loop.

If the inner loop breaks at any point, the else clause of the inner loop is skipped and the outer loop would reach the break statement, which breaks the outer loop as well.

PEP 3136 is a Python Enhancement Proposal that proposes support for labels in break and continue statements. However, it was rejected due to the complexity it would add to Python.

The example I have shown above is one of the workarounds that produce clean code. Another solution is to put the code in a function and use return instead. Using a function is usually preferred because code with low cyclomatic complexity is usually more maintainable.