Control Flow

Author

Davide Vitiello, Mirai Solutions GmbH

Published

March 11, 2025

There’s more to control flow than if-else statements and for loops. Conditional expressions and loops with break and else clauses add expressivity and compactness, and the use of try-except allows handling of errors.

Conditional Expression

Also known as ternary operator, this construct takes the form x if C else y returning either x or y depending on condition C.
Conditional expressions are more compact than if-else clauses, and can directly assigned.

x=5
print(f"{x} is {'even' if x % 2 == 0 else 'odd'}")
5 is odd
x=6
result = "even" if x % 2 == 0 else "odd"
print(f"{x} is {result}")
6 is even

The condition (C) is evaluated first. If C is True, only x is evaluated, otherwise only y is evaluated. This allows syntax of the type x[0] if len(x) > 0 else None to work seamlessly:

def first_element(x):
    return x[0] if len(x) > 0 else None
x = [1, 2, 3]
print(f"first of {len(x)} elements is {first_element(x)}")
first of 3 elements is 1
x = []
print(f"first of {len(x)} elements is {first_element(x)}")
first of 0 elements is None

Note that next() can provide a valid and more idiomatic alternative to x[0] if len(x) > 0 else None as next(iter(x), None):

def first_element(x):
    next(iter(x), None)

Usage of Loops with break and else

Loops in Python can also include an else block, which is run if the loop runs without hitting a break statement.

def find_first_multiple_of_7(numbers):
    for n in numbers:
        if n % 7 == 0:
            print(f"Found {n}")
            break
    else:
        print(f"Not found")
find_first_multiple_of_7([23, 36, 94, 83, 18])
Not found
find_first_multiple_of_7([23, 36, 91, 83, 21])
Found 91

For each number n, the function checks if n is divisible by 7, in which case the if block runs, breaking the loop.
If the if condition never applies, the else block runs instead.

try, except, else and finally

The use of try, except, else and finally give us control over our software’s behavior when something goes wrong, allowing us to capture raised exceptions (i.e. errors).

The try block contains the code that might raise an exception. If an exception is raised, the code in the corresponding except block runs, where the exception is handled.
The except block might return an error message or raise another exception providing more specific information on why the error happened. By combining different except to capture different types of exceptions, we can customize the behavior for each exception type.
The else block runs if no exceptions are raised, allowing us to execute code that depends on the successful completion of the try block.
The code under finally runs regardless of whether an exception occurred or not, and is typically used for cleanup actions that should always be performed.

def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError as e:
        print(f"Error: Cannot divide by zero. {e}")
        return None
    except TypeError as e:
        print(f"Error: Invalid input type. {e}")
        return None
    else: 
        # runs only when no exceptions are raised
        print("Division successful!")
        return result
    finally:
        # runs regardless of whether an exception occurred or not
        print("Execution of divide_numbers is complete.")

print(divide(10, 2))  
Division successful!
Execution of divide_numbers is complete.
5.0

Let’s also see the case where the division by zero raises an exception:

print(divide(10, 0))
Error: Cannot divide by zero. division by zero
Execution of divide_numbers is complete.
None

And the case where the input is not a number:

print(divide(10, 'a'))
Error: Invalid input type. unsupported operand type(s) for /: 'int' and 'str'
Execution of divide_numbers is complete.
None
Back to top