Looping

Overview:

  • Teaching: 15 min
  • Exercises: 10 min

Questions

  • How can I make a program repeat the same task many times?

Objectives

  • Explain what for loops are normally used for.
  • Trace the execution of loops and correctly state the values of variables in each iteration.
  • Write for loops that accumulate values.

What is a loop and why do we use them?

  • Doing calculations on the values in a list one by one is as painful as working with pressure_001, pressure_002, etc.
  • A for loop tells Python to execute some statements once for each value in a list, a character string, or some other collection.
  • “for each thing in this group, do these operations”

First let's write a loop and considering the equivalent code.

In [1]:
for number in [2, 3, 5]:
    print(number)
2
3
5
In [2]:
print(2)
print(3)
print(5)
2
3
5

We can see that the loop we have written:

for number in [2, 3, 5]:
    print(number)

produces the same output as explicitly print the numbers, 2, 3 and 5.
But what is the loop actually doing and what are its principle features?

  1. The collection, in this case the list [2, 3, 5] is the set of values that the loop is being run on.
  2. The body, here print(number), specifies what to do for each value in the collection.
  3. The loop variable, number, changes with each iteration in the loop taking the values specified by the collection

Python's loop syntax

The collection, body and loop variable are general features of loops however we also need to know the particular syntax implemented by the programming language we are using.

  1. Python uses the keyword for to specify that we are entering a loop.
  2. This is followed by the name of the loop variable we wish to use in the loop.
  3. Python requires the keyword in to follow the loop variable.
  4. This is followed by the collection of values that we wish to loop over.
  5. Python then requires a colon : to indicate that we have specified the conditions of the loop and are ready for the body that we want to execute at each iteration.
  6. Each line of the body must be indented, by default Ipython will add 4 spaces for you.
  7. When the indent stops the body of the loop ends.

This is an example of what makes Python particularly readable as it reads like natural language, 'for (each) value in the set [ ... ]: Do something'

In [1]: for number in [2, 3, 5]:
   ...:    #In Ipython the cursor automatically indents to help you enter the body of the loop correctly #

While the particular syntax of programming languages differ, they all have the common features, specification of the loop variable and collection, and something to indicate the stop and start of the body.

As we may be becoming to expect, Python will try and help us out if we make a mistake. For instance, what happens if we forget the colon : after specifying the conditions of the loop:

In [3]:
for number in [2, 3, 5]
  File "<ipython-input-3-63d4972f10ff>", line 1
    for number in [2, 3, 5]
                           ^
SyntaxError: invalid syntax

Python recognises that we have made a mistake, tells us that there is a SyntaxError: invalid syntax and also uses the up arrow to point to where in the line it thinks there is something missing.

If we forget to indent the body then Python does an even better job with helping us:

In [4]:
for number in [2, 3, 5]:
print(number)
  File "<ipython-input-4-3a0b55365d6d>", line 2
    print(number)
        ^
IndentationError: expected an indented block

Python flags the mistake with the IndentationError: expected an indented block and the up arrow points to where Python expects the indented body of the loop to begin.

Finally, if you indent by mistake Python will also recognise this and let you know:

In [5]:
name = "James"
 print("Hello", james)
  File "<ipython-input-5-6ae2240856a7>", line 2
    print("Hello", james)
    ^
IndentationError: unexpected indent

Multi-line bodies

The body of a loop is not restricted to a single line:

In [6]:
primes = [2, 3, 5]
for p in primes:
    squared = p ** 2
    cubed = p ** 3
    print(p, squared, cubed)
2 4 8
3 9 27
5 25 125

The three lines of code that make up the body are all executed at every iteration of the loop. We have also introduced a new operator ** - this isn't a typo but as you may have deduced from the context, it is the power operator.

Note however that if we you multiple lines of code in the body and you forget to indent the last one, Python cannot know which lines you meant to be part of the body and which weren't:

In [7]:
primes = [2, 3, 5]
for p in primes:
    squared = p ** 2
    cubed = p ** 3
print(p, squared, cubed)
5 25 125

The final line of what was meant to be the body is now only executed after the loop has completed, so it only call the last line, once, with the final value that the loop variable takes.

Using range to specify the collection

If we have three values in the collection and they are prime numbers then writing them out isn't too difficult, but what if we want to execute some code hundreds or thousands of times. This is the real power of loops and Python provides the function range to help us out. Consider the following, which accumulates the values (number + 1) in total.

In [8]:
# Sum the first 10 integers
total = 0
for number in range(10):
    total = total + (number + 1)
print(total)
55

range(integer) has been used to create the collection that our loop variable, number is looping over but what does range do?

We can investigate by printing it and finding out its type:

In [9]:
print( "Range(10) displays as:", range(10), "and is of type:",type( range(10) ))
Range(10) displays as: range(0, 10) and is of type: <class 'range'>

This is not hugely informative, though our range(10) has been expanded to range(0,10). What we need to know is that Python effectively treats range as if it were a list. As with other types we can use list to turn one type into another:

In [10]:
print( "Range(10) displays as:", range(10))
print("but we can think of it as a list:", list( range(10) ))
Range(10) displays as: range(0, 10)
but we can think of it as a list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

We can now see that range(10) is treated as a list of the first 10 integers, if we assume that we need to start counting at 0 because were are programming in Python. It also explains why we need to add 1 to number each time we iterate the loop, because in Python we start counting from 0 but when we ask to sum the first N integers we expect to start from 1.

We can also demonstrate the value of the loop structure by changing our original sum:

In [11]:
# Sum the first 10,000 integers
total = 0
for number in range(10000):
    total = total + (number + 1)
print(total)
50005000

A range of possibilities

We can do much more with range however, as hinted by the expansion above of range(10) to range(0, 10). Range accepts up to three parameters,

range(start=0, stop, step=1)

If we only specify one parameter then it will be taken as the stop value and start will default to 0, like the call to range(10) we talked about above. If we specify two values then these will be start and stop (in that order)! Finally if we include a third it will be the step, which otherwise defaults to 1.

A range of possiblities

Think about what the following might do, then try them to see if you are correct:

print(list( range(0, 10, 1) ))
print(list( range(0, 10, 2) ))
print(list( range(10, 0) ))
print(list( range(10, 0, -2) ))
print(list( range(1, 2, 0.1) ))

Solution

Looping over lists

One way in which loops become useful for us, is when we use them to loop over a dataset and perform some analysis. Let's return to our list of pressures in the previous episode and calculate the mean. In programming there are many ways of performing a given task and we will consider an example.

In [17]:
pressures = [0.273, 0.275, 0.277, 0.275, 0.276]
num_pressures = len(pressures)
sum_pressures = 0.0
for index in range( num_pressures ):
    sum_pressures = sum_pressures + pressures[ index ]
    
mean_pressure = sum_pressures / num_pressures
print("The mean pressure is:", mean_pressure)
The mean pressure is: 0.2752

Looping over a list

Recalling our first example of a loop, can you find the mean pressure without using the len function? Verify that your code gives the same result as the example above.

Solution

Looping over a string

Recalling the previous episode where we said that strings can be considered as list, print each character in "Hello world!" on a new line.

Solution

Key Points:

  • A for loop executes commands once for each value in a collection.
  • A for loop is made up of a collection, a loop variable, and a body.
  • Loop variables can be called anything (but it is strongly advised to have a meaningful name to the looping variable).
  • The body of a loop can contain many statements.
  • The first line of the for loop must end with a colon, and the body must be indented.
  • Indentation is always meaningful in Python.
  • Use range to iterate over a sequence of numbers.
  • The accumulator pattern turns many values into one.