4. Control the flow of things#

Besides data, flow control is at the heart of programming. What is the use of a list if you can’t iterate it? How could you ever take alternative courses of action based on some variable value without the use of if/else blocks?

This chapter deals with all aspects concerning control of program flow or in short flow control.

These are the constructs that you will see:

  • iteration (repeated execution) using for and while, and use cases for a closing else

  • decisions using if, elif, else and its shorthand if...else

  • fine-tuning these constructs using break, continue and pass

4.1. Code blocks#

All flow control elements make use of code blocks: one or multiple statements (lines) that belong together and are executed together.

A code block is defined by indentation (usually 4 spaces).
In flow control it always follows an iteration or decision statement ending with a colon.

<some flow control element>:
    statement belonging to block 
    statement belonging to block 
    ...

code not belonging to the flow control element

Indent consistently

A code block always starts with a colon : and the subsequent lines belonging to the same block must be indented in exactly the same way. It is recommended to use 4 spaces. Most code editors will substitute a tab for four spaces.

4.2. Iteration with for and in#

The keywords for and in are at the heart of iteration. All constructs involving for have this general structure:

for element in iterable:
    #code block with the current value of element

Let’s walk through some examples to demonstrate iteration and its details. Note that some examples can be implemented much more efficiently than shown here!

The alternative to for, while is used when you have no collection but want to repeat for another reason. This will be demonstrated at the end of this section.

Here are some fruits again:

fruits = ['kiwi', 'banana', 'apple', 'pear']

and here is iteration on the fruits:

for fruit in fruits:
    print(f'Jummy, a {fruit}!')
Jummy, a kiwi!
Jummy, a banana!
Jummy, a apple!
Jummy, a pear!

What if we want to keep a counter? Don’t do this:

i = 0
for fruit in fruits:
    i += 1
    print(f'fruit {i} is {fruit}')

Instead, use the function enumerate() to wrap your iterable:

for i, fruit in enumerate(fruits):
    print(f'fruit {i} is {fruit}')
fruit 0 is kiwi
fruit 1 is banana
fruit 2 is apple
fruit 3 is pear

The enumerate() function wraps the iteration of your collection and provides a counter value for each loop execution. It returns a tuple of (counter_value, element) that you can automatically unpack, as in for i, fruit in enumerate(fruits).

Automatic unpacking of tuples

Since functions can return only one thing, multiple return values are often wrapped into a tuple.

When you call a function that returns a tuple, such as this one

def get_tup():
    return ('foo', 'bar')

you can catch the tuple as a whole

r = get_tup()

or unpack it automatically (implicit or explicit)

one, two = get_tup()
# same
(one, two) = get_tup()

4.2.1. Exiting loops and other blocks#

If you are not interested in all items, but want to exit when you found a specific item? Use the break keyword:

for fruit in fruits:
    print(fruit)
    if fruit == 'banana':
        break
kiwi
banana

As you can see, break skips all subsequent iterations. Similarly, if you want to skip only a single iteration you can use continue:

for fruit in fruits:
    if fruit == 'banana': 
        continue
    print(fruit)
kiwi
apple
pear

For many of these flow control structures there is a shorthand if the code block to execute only constitutes a single statement. The above could also have been written as

for fruit in fruits:
    if fruit == 'banana': continue
    print(fruit)

However this gives less readable code in my opinion. There are some useful cases for the shorthand form. One of these is demonstrated in the next snippet.

age = 17

infix = "" if age > 18 else "not "

print(f'you are {infix}allowed to drink alcohol')
you are not allowed to drink alcohol

4.2.2. Using else with a for loop#

If you want to have a block executed after the for loop, you can of course simply place it below the block. However, if you want to have it executed only when no break has been issued you need and else clause:

for fruit in fruits:
    if fruit == "papaya":
        break
else:
    print("papaya was not found")
papaya was not found

4.2.3. The while loop#

If you want to repeat without a collection the while loop is your friend. Especially with user input this one comes in handy. The else clause can be used here as well (not demonstrated). Here is a typical use of while:

name = ""
while len(name.strip()) < 3:
    name = input("please give your name: ")

print(f'Welcome, {name}')

4.3. Conditionals#

Deciding what to next, based on the value of a variable, some user input, the content of a file, the availability on a remote data source. You need to do these things all the time and this is where you use conditionals.

The if/else syntax has these elements and their restrictions in occurrences:

if condition:    # One is mandatory and only one allowed
    pass
elif condition:  # Optional, but many allowed
    pass
else:            # Optional, when all conditions False, only one allowed
    pass

In this setup, only a single block will ever execute: the first one to evaluate to True, or the else clause.

age = 17
if age >= 17:
    print("You can take driving lessons")
elif age >= 18:
    print("You are allowed to drive, when you have licence of course")
else:
    print("You may not drive a car alone yet")
You can take driving lessons

If you have a situation where you want more blocks to run you need to chain multiple ifs together:

age = 17
if age >= 17:
    print("You can take driving lessons")
if age >= 18:
    print("You are allowed to drive, when you have a licence of course")
else:
    print("You may not drive a car alone yet")
You can take driving lessons
You may not drive a car alone yet

4.3.1. Optional: Switch(ing)#

You can skip this part if you want. It is a useful and powerful technique, but not essential for a beginning programmer.

If you have a distinct set of possibilities other programming languages provide the switch statement. In Python up to version 3.10 this was not available. In the (not-executable in my Python version) snippet below it is shown for Python >= 3.10.

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        # If an exact match is not confirmed, this last case will be used if provided
        case _:
            return "Something's wrong with the internet"
        
http_status(400)

4.3.2. Switching using basic Python#

Below, you see how you can create switch-like structures using a dict and adding functions as values in it. Again this uses local-defined functions.

You don’t see this type of structure very often but they’re perfectly valid as substitute for if/else or match/case. A typical use case for this would be a decision-tree that you have to execute many times in a program, for instance messaging to the user in a verbose or non-verbose manner.

def demo_old_switch(fruit):
    # method-local defined functions
    def banana():
        print("Banana yummie!")
    def kiwi():
        print("Kiwi only when juicy and sweet")
    def default():
        print("I don't know this fruit")
    
    # put the function names in a dict
    actions = {
        "banana": banana,
        "kiwi": kiwi,
        "default": default
    }
    
    actions.get(fruit, "default")()     # This looks funny doesn't it?

    
demo_old_switch("kiwi")
Kiwi only when juicy and sweet

4.4. Key concepts#

Important

  • code block: an equally indented block of code executed as a single unit. Used in flow control and functions.

  • flow control: controlling the flow a a program using loops or decision structures.

  • iteration: sequentially looping over a collection of elements.