Chapter 6. Control Flow

Chapter 6. Control Flow#

Conditional Statements#

Very often, we want to execute some code depending on whether a condition is True or False. Let’s say that we have a function process_event which takes a user event in a game and processes that event. Depending on the kind of the event we receive we would need to execute different code.

This can be achieved by using the if statement. This allows us to specify various tests and execute code depending on whether those tests succeed or fail:

def process_event(event):
    if event == "mousemoved":
        print("User moved his mouse!")
    elif event == "keypressed":
        print("User has pressed a key!")
    else:
        print("User did something else!")

First, we check whether event == "mousemoved" is True. If it is we execute print("User moved his mouse!") and skip the remaining tests.

If it isn’t, we check whether event == "keypressed" is True. If it is we execute print("User has pressed a key!").

Finally, if we don’t pass any of the checks, we move on to the body of the else statement and execute print("User did something else!").

process_event("mousemoved")
User moved his mouse!
process_event("keypressed")
User has pressed a key!
process_event("touchpadpressed")
User did something else!

We can have an arbitrary number of elif statements. For example this would work just fine:

def process_event(event):
    if event == "mousemoved":
        print("User moved his mouse!")
    elif event == "keypressed":
        print("User has pressed a key!")
    elif event == "keyreleased":
        print("User has released a key!")
    elif event == "quit":
        print("User quit the game!")
    else:
        print("User did something else!")
process_event("quit")
User quit the game!

The elif and else statements are not needed. We can have an if statement without anything else:

def process_event(event):
    if event == "mousemoved":
        print("User moved his mouse!") 
process_event("mousemoved")
User moved his mouse!

If we pass an event not covered by the if or elif blocks and we don’t have an else block, the code will simply do nothing:

process_event("somethingelse")

Generally speaking, the tests can be any expression that evaluates to a boolean value. As long as the resulting value will be True, the statement(s) in the body will be executed:

if True:
    print("This will be printed")
This will be printed
if True or False:
    print("True or False evaluates to True")
    print("Therefore the two lines will be printed")
True or False evaluates to True
Therefore the two lines will be printed
if False:
    print("This will not be printed")

If statements can also be arbitrarily nested:

def process_event(event):
    if event["type"] == "keypressed":
        if event["key"] == "SPACE":
            print("User pressed space")
        elif event["key"] == "UP":
            print("User pressed up arrow")
        else:
            print("User pressed something else")
    else:
        print("User did something else")
process_event({"type": "keypressed", "key": "SPACE"})
User pressed space

Note that the nesting is defined by the indentation. If you write nested if statements, you should check whether your indentation is correct.

Beware of if statements that are nested too deeply as such code will quickly become very hard to read and maintain.

For Loops#

Another important control flow construct is the for loop. In Python, the for loop can be used to iterate over iterables and execute a block of code for each iteration.

Here is how we could iterate over a list and print each element of that list:

values = [0, 1, 4, 9, 16]
for square in values:
    print(square)
0
1
4
9
16

We can also iterate over ranges. This is commonly used if you need to iterate over a bunch of numbers and do something depending on the value of each number:

for i in range(4):
    print(i)
0
1
2
3

For example here is how we could create the squares list automatically:

squares = []
for i in range(5):
    squares.append(i ** 2)
squares
[0, 1, 4, 9, 16]

We can also iterate over dictionaries. By default, iterating over a dictionary will iterate over its keys:

ratings = {"Alex": 1500, "Michael": 1400, "John": 1000, "Max": 1200}
for name in ratings:
    print(name)
Alex
Michael
John
Max

We can also iterate over the values or key-value pairs of a dictionary using the values and items methods respectively:

for rating in ratings.values():
    print(rating)
1500
1400
1000
1200
for name, rating in ratings.items():
    print(f"name={name}, rating={rating}")
name=Alex, rating=1500
name=Michael, rating=1400
name=John, rating=1000
name=Max, rating=1200

Comprehensions#

Instead of populating a list using a for loop, we can write a list comprehension:

squares = [i ** 2 for i in range(5)]
squares
[0, 1, 4, 9, 16]

This is a feature that is pretty specific to Python and allows us to express complex control flow very concisely. Consider the difference between the following two constructs:

# Regular for loop
squares = []
for i in range(5):
    squares.append(i ** 2)
# List comprehension
squares = [i ** 2 for i in range(5)]

The second one is not only shorter, but also more readable because the intent of the code is clearer - we wish to create a new list. This is all the more powerful since list comprehensions can take conditions as well:

even_squares = [i ** 2 for i in range(5) if i % 2 == 0]
even_squares
[0, 4, 16]

Contrast this with what we would need to do if we wanted to create this list inside a regular for loop:

even_squares = []
for i in range(5):
    if i % 2 == 0:
        even_squares.append(i ** 2)
even_squares
[0, 4, 16]

Whenever we need to create a new list based on some values, we will utilize a list comprehension.

Dictionary comprehensions allow us to write similar code for creating new dictionaries:

squares_dict = {k:k ** 2 for k in range(5)}
squares_dict
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

While Loops#

Finally, while loops allow us to execute a block of code as long as a condition is True.

Let’s say we have a list of items which have a certain weight and a knapsack with a limited maximum weight. We want to find out which items we can pack into the knapsack without exceeding that maximum weight. We would move over the list while the maximum weight is not exceeded and add the items:

current_weight = 0
maximum_weight = 100
item_weights = [10, 20, 30, 20, 40, 20, 30]
index = 0

while index < len(item_weights) and current_weight + item_weights[index] <= maximum_weight:
    current_weight += item_weights[index]
    index += 1
current_weight
80
index
4
item_weights[:index]
[10, 20, 30, 20]

A common example of a while loop is the main loop of a game. Here we would have a flag that determines if the game is active or not. The body of the while loop then updates the game. If the player wins or loses the game, the flag is set to False and we will exit the while loop on the next iteration:

active = True

while active:
    # update game, this sets active = False here
    # (e.g. if player wins or loses the game)
    
    ...
    
    # rerender game