Code Structures I: Conditionals, Loops, and Comprehensions

1 Checking cases: if, elif, and else

1.1 The simple case with if and else

We start with one of the simplest code structures: if and else. These statements are used to check the truth-value of a particular condition and, depending on whether it is evaluated to be True or False, run a code block.

recession = False
if recession:
    print('Booh!')
else:
    print('Yay!')
Yay!

What did the code do here? In the first line, we assigned the boolean False to the variable recession. In the second line, we check the truth value of the expression recession. Since it is equal to False, the indented code block starting in the third block is not executed. Instead, the indented code behind the else: expression is executed.

Note that the expression whose truth value we check is not written in parentheses as in other languages but concluded with a colon. Moreover, the code to be executed after the if and else statements is also not set in brackets but indented. This gives the code a very clean and tidy look.

Of course, you can also nest if-else structures within each other but I won’t show it here as it is straightforward.

1.2 Checking several cases with elif

So, what if you want to check more than one case? Use elif!

color = 'violet'
if color == 'red':
    print('It is a tomatoe!')
elif color == 'yellow':
    print('It is a yellow pepper!')
elif color == 'violet': 
    print('It is an onion!')
else:
    print('No idea what this is.')
It is an onion!

We assigned the string 'violet' to the variable 'color'. The second line checked whether the variable color is equal to the string 'red'. This was evaluated to be False so Python did not execute the indented code and jumped to the next condition. It then checked the second expression. Color was also not equal to yellow it again skipped the indented code. Finally, the third expression turned out to be True, so Python printed the string 'It is an onion!'.

1.3 Comparison operators

In the previous example, we used the equality operator ==. Let’s briefly run through the different comparison operators that Python has to offer:

  • ==: equality

  • !=: inequality

  • <: less than

  • <=: less than or equal

  • in: membership.

Finally, to combine expressions you want to check, use the boolean operators and and or. If you want to negate a statement, use not. So, let’s play around a little bit.

x = 7
x < 7
False
x <= 7
True
number_list = [5, 6, 7]
x in number_list
True
x not in number_list
False
x < 5 or x >= 7
True
x < 5 and x >= 7
False
5 < x < 10
True

It might make sense to use parentheses in order to organize the precedence of expressions.

(x < 5) and (x >= 7)
False

2 Repeat with while loops

Sometimes, instead of checking cases, you want to repeat a certain action until a condition has been met. You can do this with a while loop.

count = 1
while count <= 5:
    print(count)
    count += 1
1
2
3
4
5

In the first line, we assigned the value 1 to the variable count. Then, the actual loop started. In the second line, the value of the condition count <= 5 is checked. In the first run, this expression is equal to True. Therefore, the indented code is executed and the variable count is increased by one. The loop is repeatedly executed until count = 6. Then, the expression count <= 5 is equal to False and the loop ends.

If you want to repeat a loop until something happens, but you don’t know when it occurs, you can use the break command.

while True: 
    string = input('String to capitalize (type q to quit):')
    if string == 'q':
        break
    print(string.capitalize())
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
/tmp/ipykernel_2310/4008045787.py in <module>
      1 while True:
----> 2     string = input('String to capitalize (type q to quit):')
      3     if string == 'q':
      4         break
      5     print(string.capitalize())

/opt/hostedtoolcache/Python/3.8.11/x64/lib/python3.8/site-packages/ipykernel/kernelbase.py in raw_input(self, prompt)
   1001         """
   1002         if not self._allow_stdin:
-> 1003             raise StdinNotImplementedError(
   1004                 "raw_input was called, but this frontend does not support input requests."
   1005             )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

This loop is repeated infinitely since the expression True is always True. However, Python let’s you break out of the loop with break if a certain condition is met. In this case, this condition is that you type in the string 'q'. We can combine our code snippet with continue to skip the rest of the loop and directly start with the next iteration.

while True: 
    value = input('Odd integer, please (type q to quit):')
    if value =='q':
        break
    number = int(value)
    if number % 2 == 0:
        continue
    print(number, ' squared is ', number * number)
Odd integer, please (type q to quit):2
Odd integer, please (type q to quit):3
3  squared is  9
Odd integer, please (type q to quit):q

3 Iterate with for loops

3.1 Looping over lists

The use of a for loop is particularly helpful if you want go through an iterable object, such as a list. To see the situation, look at the following while loop which goes through a list and prints out each element.

economists = ['Hayek', 'Keynes', 'Marshall', 'Krugman']
current = 0
while current < len(economists):
    print(economists[current])
    current += 1
Hayek
Keynes
Marshall
Krugman

While this code snippet works it is certainly not elegant, since we have to create and repeatedly increment the counter current to loop through the list. A simpler way using a for loop would be as follows.

economists = ['Hayek', 'Keynes', 'Marshall', 'Krugman']
for economist in economists: 
    print(economist)
Hayek
Keynes
Marshall
Krugman

This loop automatically creates the variable economist which with each iteration of the loop takes the value of a different element from the lists economists. The loop proceeds until it has gone through the whole list.

3.2 Looping over strings and dictionaries

There are also other iterable objects, such as strings or dictionaries. In the case of a string, the iteration loops through the characters of the string.

word = 'Samuelson'
for letter in word: 
    print(letter)
S
a
m
u
e
l
s
o
n

So what happens if we loop over a dictionary?

biography = {'name' : 'Hayek', 'affiliation' : 'University of Freiburg', 'nationality' : 'Austrian'}
for element in biography: 
    print(element)
name
affiliation
nationality

As you can see, a for loop without qualification runs through the keys of the dictionary. What if we want to loop through the values?

biography = {'name' : 'Hayek', 'affiliation' : 'University of Freiburg', 'nationality' : 'Austrian'}
for element in biography.values():
    print(element)
Hayek
University of Freiburg
Austrian

Finally, what if we want the loop to return both the key as well as the corresponding values? Use the items function!

biography = {'name' : 'Hayek', 'affiliation' : 'University of Freiburg', 'nationality' : 'Austrian'}
for element in biography.items():
    print(element)
('name', 'Hayek')
('affiliation', 'University of Freiburg')
('nationality', 'Austrian')

In this case, each item is assigned a tuple with two values. What if we want to have both elements separately? Remeber the cool assignment trick for tuples?

biography = {'name' : 'Hayek', 'affiliation' : 'University of Freiburg', 'nationality' : 'Austrian'}
for key, value in biography.items():
    print('The key is ' + key + ' and the corresponding value is ' + value + '.')
The key is name and the corresponding value is Hayek.
The key is affiliation and the corresponding value is University of Freiburg.
The key is nationality and the corresponding value is Austrian.

3.3 Looping over several lists with zip

What if we want to loop over several lists at once? This is possible with the zip function.

economists = ['Hayek', 'Keynes', 'Marshall', 'Krugman']
icecreams = ['chocolate', 'vanilla', 'coconut', 'cookie']

for economist, icecream in zip(economists, icecreams): 
    print(economist + ' loves ' + icecream + ' icecream.')
Hayek loves chocolate icecream.
Keynes loves vanilla icecream.
Marshall loves coconut icecream.
Krugman loves cookie icecream.

These sweet-tooths…

3.4 Generating sequences of numbers with range

Sometimes you want to iterate through a sequence of numbers. These sequences can be easily created with the range function.

# range(start, stop, step)
# default value is 0 for start, and 1 for step
for value in range(3):
    print(value)
0
1
2

As with slicing, the right side of the offset range is not included.

for value in range(2, 4, 1):
    print(value)
2
3
for value in range(4, -1, -1):
    print(value)
4
3
2
1
0

By the way, the range function can also be used to create lists of sequences quickly.

list(range(3))
[0, 1, 2]

4 Comprehensions

4.1 List Comprehensions

There is another feature of the Python language which sometimes helps you in avoiding complicated structures of loops and conditional tests: comprehensions. This is already an advanced topic but once you get it, you will (might) love it! Let’s create a list with the numbers 1 to 6. We already now several ways to do this.

number_list = []
number_list.append(1)
number_list.append(2)
number_list.append(3)
number_list.append(4)
number_list.append(5)
number_list.append(6)
print(number_list)
[1, 2, 3, 4, 5, 6]
number_list = list(range(1,7))
print(number_list)
[1, 2, 3, 4, 5, 6]
number_list = []
for number in range(1, 7):
    number_list.append(number)
print(number_list)
[1, 2, 3, 4, 5, 6]

Now, the easiest way to do the same thing using a comprehension is as follows.

number_list = [number for number in range(1,7)]
print(number_list)
[1, 2, 3, 4, 5, 6]

The general syntax is [expression for item in iterable]. You might not see the advantage yet. We will get there. First, note that expression can also be something more complicated. In the following, I create a list with the squares of the numbers from 1 to 6.

number_list = [number**2 for number in range(1,7)]
print(number_list)
[1, 4, 9, 16, 25, 36]

Secondly, we can extend the basic structure to [expression for item in iterable if condition]. Let’s say we want to construct a list out of all the odd numbers between 1 and 20.

number_list = [number for number in range(1, 21) if number % 2 == 1]
print(number_list)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Finally, you can also have several iterables. Let’s say we want to loop through every combination of columns and rows.
The first way to do this would be with nested for loops.

rows = range(1, 5)
cols = range(1, 3)
for row in rows: 
    for col in cols: 
        print(row, col)
1 1
1 2
2 1
2 2
3 1
3 2
4 1
4 2

A similar thing can also be done with list comprehensions, where we create a tuple for every combinations of rows and columns.

rows = range(1, 5)
cols = range(1, 3)
cells = [(row, col) for row in rows for col in cols]
print(cells)
[(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2), (4, 1), (4, 2)]

If we want to have exactly the same result, we have to unpack the tuples again.

for row, col in cells:
    print(row, col)
1 1
1 2
2 1
2 2
3 1
3 2
4 1
4 2

4.2 Dictionary Comprehensions

Similar structures can also be used to create dictionaries. The expression is {key_expression : value_expression for item in iterable}. Say we want to store the numbers from 1 to 10 together with their squares.

squares_dict = {number : number**2 for number in range(1,11)}
print(squares_dict)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}

Sources

The structure of exposition was taken from Bill Lubanovic (2015): Introducing Python: Modern Computing in Simple Packages. O’Reilly: Sebastopol, CA.