Program Control Logic

Conditionals

Often in life our behaviour depends on, for example, the weather, or other people's actions, or the time of day. The same happens in programming -- we want some piece of code to be executed only in the right circumstances. So just as I might say "If it is raining, then I will take an umbrella. Otherwise, if it is sunny, I will wear sunglasses.", so Python provides the if statement to allow for conditional execution of code.

If

The basic structure of an if statement is shown below. First, the if keyword, followed by the condition to be checked and then a colon. The colon must be included or an error will occur. The code which will be executed if the condition is met is indented, all lines of this section of code have to be indented by the same amount. The indentation is essential, this is how Python knows which bit of code belongs to the if block. Other languages might use parentheses or a special ending token to show which part of the code belongs to the if block and which code should always be executed, but Python relies entirely on the indentation. Later code is written without this indentation and this is no longer part of the if statement so it will be executed in all cases.

if <condition>:
    <code to execute when condition is True>
<code that always executes whether condition is True or False>

The expression in <condition> can be anything that evaluates to a Boolean (True or False). In fact, Python gives some flexibility here because other values are treated as True or False when the context requires a Boolean. For example, the integer 0 is treated the same as the Boolean value False, and all other integers are treated the same as True. The empty string '' evaluates to False, all other strings evaluate to True. Similarly, the empty list [] and empty dictionary {} both evaluate to False, all other lists and dictionaries evaluate to True.

Sometimes this convenience is really helpful and allows for code that is more readable and succinct, other times it can be a source of bugs. In general, <condition> should return a Boolean. If it will return any other type (int, string, float, list...) then remember to check carefully how the different possible values will be interpreted.

Elif

We might want to check a few different conditions, as in the earlier example "...Otherwise, if it is sunny, I will wear sunglasses." In python this is achieved by elif (short for "else, if"). The elif keyword is used after if, it can't be used on its own. Again, the line that starts with elif also contains an expression that evaluates to True or False, and must end in a colon. The following line is indented, and will only be executed if 1) the condition in the if line evalutes to False and 2) the condition in the elif line evaluates to True. Any subsequent lines indented by the same amount also form part of the same block of code belonging to the elif statement. When the indentation is reduced again then we're no longer within the elif block.

if <condition_1>:
    <code to execute when condition_1 is True>
elif <condition_2>:
    <code to execute when condition_1 is False AND condition_2 is True>
<code that always executes>

Notice that the conditions are being checked in order. In our weather example we said "If it is raining, then I will take an umbrella. Otherwise, if it is sunny, I will wear sunglasses." What should happen if it is both sunny and raining at the same time? Because of the word otherwise the logical interpretation is that in this case I will only take an umbrella, I'll only wear sunglasses in the case that it's not raining. The if ... elif structure does not allow for the block belonging to the if part and the block belonging to the elif part to both be executed.

Maybe there are many different scenarios to check -- then we just keep adding elif statements, there's no limit on how many can be used.

if <condition_1>:
    <code to execute when condition_1 is True>
elif <condition_2>:
    <code to execute when condition_1 is False AND condition_2 is True>
elif <condition_3>:
    <code to execute when condition_1 is False AND condition_2 is False AND condition_3 is True>

... other elif statements...

<code that always executes>

Maybe it's more sensible that when the weather is both raining and sunny I should take both an umbrella and sunglasses. Then I would say "If it is raining, then I will take an umbrella. If it is sunny, I will wear sunglasses...". The same works in Python -- just use multiple, unrelated if statements.

if <condition_1>:
    <code to execute when condition_1 is True>

if <condition_2>:
    <code to execute when condition_2 is True>

Else

Finally, we may have some default behaviour. For example, "If it is past 7am I will get up, otherwise I will go back to sleep". In Python this is done using the keyword else. No condition is needed after else, but it must be followed by a colon. The code on the next line(s) should be indented. This indented block of code is executed when none of the conditions in the if and elif conditions evaluated to true.

if <condition_1>:
    <code to execute when condition_1 is True>
elif <condition_2>:
    <code to execute when condition_1 is False AND condition_2 is True>
else:
    <code to execute when condition_1 is False AND condition_2 is False>
<code that always executes>

If Statements On a Single Line

It is possible in Python to write conditionals in fewer lines, as below. If the code within an if block would have been written over several lines then these statements can be separated with semicolons instead.

if <condition_1>: <code to execute when condition_1 is True>; <more code to execute when condition_1 is True>
elif <condition_2>: <code to execute when condition_1 is False AND condition_2 is True>
else: <code to execute when condition_1 is False AND condition_2 is False>
<code that always executes>

Don't do this. It's much less readable than using separate lines and indentation. Most Python programmers will only very occasionally use this style, for example when the if block is extremely simple. It's better to avoid it entirely and get comfortable with using indentation to oranise your code blocks.

Nested Conditionals

The code that will be executed as part of an if (or elif, or else) block can contain any valid Python code. In particular, it can contain more if statements. Python will understand which code belongs to which if block by the indentation.

if <condition_1>:
    <code to execute when condition_1 is True>

    if <condition_1a>:
        <code to execute when condition_1 is True AND condition_1a is True>
    elif <condition_1b>:
        <code to execute when condition_1 is True AND condition_1a is False AND condition_1b is True>

    <more code to execute when condition_1 is True>

There is no limit to how deeply nested if statements can be, but if you have many nested statements then the code might become difficult for humans to understand which makes bugs more likely. It's usually a good idea to avoid nesting

Loops

Often it is necessary to repeat the same action many times over. To avoid writing the same piece of code multiple times, and to allow for a variable number of repeats, we use loops. Python provides two types of loop: while and for.

For Loops

Python for loops are used to apply the same action to every item in an iterable object. What is an iterable object? It's most easily explained as something that can be used in a for loop! More helpfully, an iterable object is a collection of objects that has the ability to return its elements one at a time. Lists, dictionaries, tuples, sets, and strings are all iterable. It's also possible to define new types of object that are iterable. To start with we'll work with lists but the ideas are very similar for other iterable types.

For Loops Over Lists

Applying a for loop to a list is a way to perform the same action (run the same block of code) to every element in a list. To create the loop, start with the keyword for followed by a new variable name. The variable name will hold the value of each item of the list in turn. This is followed by the keyword in and the list that is to be looped over. The line ends in a colon and the following line must be indented. As with if statements, the indentation is essential. All the lines that are indented form the block of code that will be executed within the loop, to indicate that the code belonging to this block has ended just reduce the indentation. If we have a list called my_list then the syntax of a for loop over it is

for list_item in my_list:
    <code to be executed within the loop. This can use the variable list_item to access the current element>
<code that will be executed after the loop has finished for all items in my_list>

For example, the following code loops over a list of names and prints the same greeting for each one.

In [1]:
list_of_names = ['Fred', 'George', 'Sam']

for name in list_of_names:
    print('Hello {}, nice to meet you!'.format(name))
Hello Fred, nice to meet you!
Hello George, nice to meet you!
Hello Sam, nice to meet you!

There's no limit to how many lines of code can be inside the for loop, and no limit on how complicated this code is. In particular, it can include more loops (see later), and conditionals.

In [2]:
list_of_colours = ['Red', 'Green', 'Blue', 'Yellow']

for colour in list_of_colours:
    if colour == 'Green':
        print('Green is my favourite colour!')
    else:
        print('{} is a nice colour.'.format(colour))
Red is a nice colour.
Green is my favourite colour!
Blue is a nice colour.
Yellow is a nice colour.

Sometimes it is useful to know the index of the current list item as well as its value. This is achieved with the enumerate() function, which takes a list and creates an iterable object that returns a sequence of tuples. The first item of the tuple is a the list index, the second item is the value at that index. Because two values are returned at each iteration we need two loop variables and these are separated with a comma. Otherwise the syntax is unchanged. Code within the loop uses the loop variables to access the current list index and list value.

for i, list_item in enumerate(my_list):
    <code to be executed within the loop>
<code that will be executed after the loop has finished for all items in my_list>

In fact enumerate will take any iterable as input, not only lists. It works in just the same way and always returns a sequence of index, value pairs.

For Loops With a Specified Number of Iterations

Instead of applying the same code to each member of a list we might simply want to repeat the block of code some number of times. This can be achieved in a variety of ways, but the neatest way is to use a for loop and Python's range function.

The range function creates an iterable object that returns a sequence of integers. It takes up to three parameters: start, stop, and step.

range(start, stop, step)

However, in the simplest case, only one parameter is required. range(n) creates an iterable object that returns, in order, the numbers from 0 to n-1. Notice that n is not included, but the total number of values returned is n. So, to create a loop that will excute a piece of code n times, we write

for i in range(n):
    <code to execute within loop>

When only one parameter is given it is assumed to be the stop parameter. The other parameters are given default values of start = 0 and step = 1. If two parameters are given then the first is the start value and the second is the stop value. In this case range(m, n) creates an iterable object that returns, in order, the numbers from m to n-1. In total n-m values are returned.

The step parameter modifies the increments between the returned numbers. So range(m, n, s) creates an iterable object that returns, in order, the numbers m, m+s, m+2s, ... with the sequence continuing until the next item would be at least n. For example, range(4, 11, 2) will return the numbers 4, 6, 8, 10.

For Loops Over Other Objects

The syntax of a for loop is the same regardless of the object type being iterated over. We've already seen three iterable types: lists, enumerate objects, range objects. For dictionaries we make use of the functions values() and items(). By default if we write a for loop to iterate over a dictionary the iterator returns the dictionary's keys. To access the values we could simply use the key to lookup the value in the dictionary. However, it is often more convenient to have the value returned by the iterator. dictionary.values() creates an iterable object that returns the values from the dictionary. dictionary.items() creates an iterable object that returns a sequence of tuples. The first item of the tuple is a dictionary key and the second item is the value associated with that key. Because two things are being returned we need two loop variables -- just like with enumerate(). If we have a dictionary called my_dict then the syntax of a for loop over the keys and values is

for key, value in my_dict.items():
    <code to be executed within the loop, this can make use of the loop variables key and value.>
In [3]:
people_and_places = {
    'John': {'home': 'Leeds', 'born': 'Paris', 'parents': 'Paris'},
    'Fred': {'home': 'Barcelona', 'born': 'Madrid', 'parents': 'Oviedo'},
    'George': {'home': 'London', 'born': 'Bristol', 'parents': 'Exeter'},
    'Sam': {'home': 'Munich', 'born': 'Berlin', 'parents': 'Munich'}
}

for name, data in people_and_places.items():
    print('{} lives in {}. He was born in {} and his parents live in {}.'.format(name, data['home'], data['born'], data['parents']))
John lives in Leeds. He was born in Paris and his parents live in Paris.
Fred lives in Barcelona. He was born in Madrid and his parents live in Oviedo.
George lives in London. He was born in Bristol and his parents live in Exeter.
Sam lives in Munich. He was born in Berlin and his parents live in Munich.

While Loops

The second type of loop available in Python is the while loop. Instead of applying a piece of code to every item in a list, or repeating it a fixed number of times, we will keep repeating the same block of code for as long as some Boolean condition evaluates to True. The loop is created with the while keyword followed by any expression that returns a Boolean, followed by a colon. Then the next line of code is indented. This line, and subsequent lines that are also indented, form the block of code that will be repeated within the loop. Python will know that the code belonging to the loop has ended when the indentation is reduced.

while <condition>:
    <code to be executed as long as <condition> is True>

Break and Continue

We've said that a while loop will continue until the condition evaluates to False, and a for loop will continue as long as the iterable still returns a next object, but there is a way to exit a loop earlier. To do this, ues the break keyword. When Python is instructed to break the loop it will immediately move to the first line of code following the loop block. Any further iterations of the loop are skipped. Usually, break is used in a conditional statement, as in the following example. Without the break statement the loop would print every number from 5 to 11. With the break statement, the loop exits early -- as soon as it has printed a number that divides by 4.

In [4]:
a = 5
while a<12:
    print(a)
    if a%4 == 0:
        break
    a += 1
5
6
7
8

continue is similar but instead of breaking out of the loop completely it just causes this iteration to be skipped. In a for loop we move onto the next item in the iterable, in a while loop we go back and check the condition again. In the next example we check whether the current value of a is divisible by 4 -- if it is then we move straight on to the next iteration, skipping out the instruction to print(a). Therefore, values between 5 and 11 that are not divisible by 4 are printed.

In [5]:
a = 5
while a<12:
    if a%4 == 0: 
        a += 1
        continue
    print(a)
    a += 1
5
6
7
9
10
11

for ... else and while ... else

We now have two distinct reasons for moving on from each type of loop. In the case of a for loop, we can either exit the loop because the iterable has been exhausted -- for example, there are no more items in the list -- or because we used a break statement to exit early. In the case of a while loop, we either exit because the loop condition evaluates to False or because we used a break statement to exit early. An else block following a loop contains code that will be executed only if the loop has exited "naturally", that is, without using a break statement.

Nested Loops

We've already seen that the code block inside a loop can contain conditionals, and it is also allowed to contain more loops. Python relies on indentation to organise code blocks and to know which lines of code belong to which of the nested loops.

The following example imagines a group of people who are all want to greet each other.

In [6]:
list_of_people = ['John', 'Fred', 'George', 'Sam']

for i, main_person in enumerate(list_of_people):
    for j, other_person in enumerate(list_of_people):
        if i == j:
            # no need to greet yourself!
            continue
        else:
            print('{} says hello to {}.'.format(main_person, other_person))
John says hello to Fred.
John says hello to George.
John says hello to Sam.
Fred says hello to John.
Fred says hello to George.
Fred says hello to Sam.
George says hello to John.
George says hello to Fred.
George says hello to Sam.
Sam says hello to John.
Sam says hello to Fred.
Sam says hello to George.

Nested loops can also be useful for unpacking structured data such as nested lists or dictionaries.

In [7]:
favourite_things = {
    'John': ['raindrops on roses', 'whiskers on kittens'],
    'Fred': ['bright copper kettles', 'warm woolen mittens'],
    'George': ['brown paper packages tied up with strings'],
    'Sam': []
}

for name, favourites in favourite_things.items():
    if favourites == []:
        print('{} has no favourite things.'.format(name))
    else:
        for thing in favourites:
            print('{} likes {}.'.format(name, thing))
John likes raindrops on roses.
John likes whiskers on kittens.
Fred likes bright copper kettles.
Fred likes warm woolen mittens.
George likes brown paper packages tied up with strings.
Sam has no favourite things.