Warning: This document is for the development version of Geo-Python. The main version is master.

This page was generated from source/notebooks/L3/for-loops.ipynb.
Binder badge
Binder badge CSC badge

for loops

Sources

This lesson is based on the Software Carpentry group’s lessons on Programming with Python.

Basics of for loops

In our last lesson we covered lists in Python, one form of a collection of values that can be referenced by a single variable. One common thing we might do with a list is to go through the values in the list and perform a calculation on each one. In this lesson we will learn how to use loops. Loops allow parts of code to be repeated some number of times.

A (bad) example

Let’s consider an example using the list below:

[1]:
european_cities = ['Helsinki', 'Paris', 'Barcelona', 'Uppsala']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

Suppose we want to print out the name of each city in our list. We could use the index value for each city and do the following:

[2]:
print(european_cities[0])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-60022db54c60> in <module>()
----> 1 print(european_cities[0])

NameError: name 'european_cities' is not defined
[3]:
print(european_cities[1])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-48f3bfe1e89e> in <module>()
----> 1 print(european_cities[1])

NameError: name 'european_cities' is not defined
[4]:
print(european_cities[2])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-4-5f4889b627e3> in <module>()
----> 1 print(european_cities[2])

NameError: name 'european_cities' is not defined
[5]:
print(european_cities[3])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-4122ab57acf3> in <module>()
----> 1 print(european_cities[3])

NameError: name 'european_cities' is not defined

But this is a bad idea. Why? Well there are two reasons. First, it does not scale nicely for long lists, and will take forever to type in. Second, it won’t work if the length of the list has fewer than 4 cities. Let’s see an example with a new list.

[6]:
european_cities = ['Riga', 'Rome', 'Athens']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[7]:
print(european_cities[0])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-60022db54c60> in <module>()
----> 1 print(european_cities[0])

NameError: name 'european_cities' is not defined
[8]:
print(european_cities[1])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-8-48f3bfe1e89e> in <module>()
----> 1 print(european_cities[1])

NameError: name 'european_cities' is not defined
[9]:
print(european_cities[2])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-9-5f4889b627e3> in <module>()
----> 1 print(european_cities[2])

NameError: name 'european_cities' is not defined
[10]:
print(european_cities[3])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-10-4122ab57acf3> in <module>()
----> 1 print(european_cities[3])

NameError: name 'european_cities' is not defined

Introducing the for loop

We could do a much better job by using a for loop.

[11]:
european_cities = ['Amsterdam', 'Brussels', 'Lisbon', 'Reykjavik']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[12]:
for city in european_cities:
    print(city)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

Not only is this shorter, but it is also more flexible. Try printing out a different list of cities such as ['Detroit', 'Chicago', 'Denver', 'Boston', 'Portland', 'San Francisco', 'Houston', 'Orlando']. Still works, right?

[13]:
us_cities = ['Detroit', 'Chicago', 'Denver', 'Boston', 'Portland', 'San Francisco', 'Houston', 'Orlando']
for city in us_cities:
    print(city)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

for loop format

for loops in Python have the general form below.

for variable in collection:
        do things with variable

The variable can be any name you like, and the statement of the for loop must end with a :. The code that should be executed as part of the loop must be indented beneath the for loop statement, and the typical indentation is 4 spaces. There is no additional special word needed to end the loop, you simply change the indentation back to normal. for loops are useful to repeat some part of the code a definite number of times.

Your daily for loop

cat's daily routine Source: https://www.bugmartini.com/comic/cats-eye/

Like many other programming concepts, the idea of looping through actions is something that is already perhaps more familiar to you than you think. Consider your actions during a given day. Many people have certain routines they follow each day, such as waking up, taking a shower, eating breakfast and brushing their teeth. In Python code, we might represent such actions as follows:

for day in my_life:
    wake_up()
    take_shower()
    eat_breakfast()
    brush_teeth()
    ...

Note that my_life would be a list of the days of your life, and the actions you take are represented as functions, such as wake_up(). Furthermore, by following this kind of list of repeating actions we’re able to start the day effectively even before the first cup of coffee :).

for loop variables

Note that the variable used in a for loop is just a normal variable and still exists after the loop has completed with the final value given to letter. Let’s loop over the list of weather conditions below and print them to the screen. If you use weather for the loop variable, what is its value after the for loop has completed?

[14]:
weather_conditions = ['rain', 'sleet', 'snow', 'freezing fog', 'sunny', 'cloudy', 'ice pellets']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[15]:
for weather in weather_conditions:
    print(weather)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[16]:
print('After the loop, weather is', weather)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-879a030701fd> in <module>()
----> 1 print('After the loop, weather is', weather)

NameError: name 'weather' is not defined

for loops and the range() function

A loop can be used to iterate over any list of values in Python. So far we have considered only lists, but we could also write a loop that performs a calculation a specified number of times by using the range() function. Let’s consider an example where we use a for loop with value as the loop variable and range(5) as the collection. What happens when you print value at each iteration?

[17]:
for value in range(5):
    print(value)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

In this case, we use a special function called range() to give us a list of 5 numbers [0, 1, 2, 3, 4] and then print each number in the list to the screen. When given a integer (whole number) as an argument, range() will produce a list of numbers with a length equal to the specified number. The list starts at 0 and ends with number - 1. You can learn a bit more about range by typing help(range).

[18]:
help(range)
Help on class range in module builtins:

class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
 |
 |  Methods defined here:
 |
 |  __bool__(self, /)
 |      self != 0
 |
 |  __contains__(self, key, /)
 |      Return key in self.
 |
 |  __eq__(self, value, /)
 |      Return self==value.
 |
 |  __ge__(self, value, /)
 |      Return self>=value.
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __getitem__(self, key, /)
 |      Return self[key].
 |
 |  __gt__(self, value, /)
 |      Return self>value.
 |
 |  __hash__(self, /)
 |      Return hash(self).
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  __le__(self, value, /)
 |      Return self<=value.
 |
 |  __len__(self, /)
 |      Return len(self).
 |
 |  __lt__(self, value, /)
 |      Return self<value.
 |
 |  __ne__(self, value, /)
 |      Return self!=value.
 |
 |  __reduce__(...)
 |      Helper for pickle.
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __reversed__(...)
 |      Return a reverse iterator.
 |
 |  count(...)
 |      rangeobject.count(value) -> integer -- return number of occurrences of value
 |
 |  index(...)
 |      rangeobject.index(value) -> integer -- return index of value.
 |      Raise ValueError if the value is not present.
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  start
 |
 |  step
 |
 |  stop


Poll question pause

The program below will print numbers to the screen using the range() function.

for i in range(...):
    print(i)

Using the documentation that is produced when you run help(range), what values would you replace the ... in the parentheses of the range() function with to have the following output printed to the screen?

2
5
8

Select your answer from the poll options at https://geo-python.github.io/poll/.


Looping over the length of lists using index values

Since we already know how to find the length of a list using the len() function, and we can now take advantage of this knowledge to make our for loops more flexible. Starting with the list of numbers below, let’s use the range() function to loop over the list of numbers and add the value of the loop variable i to each value. In addition, we can add a few print statements to display the values of i and numbers[i] within the loop. In the cell below the for loop, you can print the list of numbers again to see the updated values.

Optional: You can display ``numbers[i]`` before and after the addition.

[19]:
numbers = [0, 1, 2, 3]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[20]:
for i in range(len(numbers)):
    print('Value of i:', i)
    print('Value of numbers[i] before addition:', numbers[i])
    numbers[i] = numbers[i] + i
    print('Value of numbers[i] after addition:', numbers[i])
    print('')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[21]:
print(numbers)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-a2aabdc73a85> in <module>()
----> 1 print(numbers)

NameError: name 'numbers' is not defined

Let’s see what we can observe:

  1. You can see that because we are using the range() function, the value assigned to the loop variable i starts with 0 and increases by 1 each time through the loop.
  2. We can see the value in the list numbers at index i each time through the loop, and how that value changes when it is increased by adding i.
  3. The value that changes in the list numbers in each iteration through this for loop is the value at index i, while the other values are not updated. This occurs because we’re assigning a new value at numbers[i].
  4. Note that the values for numbers[i] on the right side of the equation is the “old” value. That “old” value is increased by i first, and then stored as the updated value numbers[i].
Note: The variable i is commonly used to denote the index variable in loops. Loops can sometimes occur with another loop (referred to as nested loops), in which case other index variables such as j or k may be used.

Why bother looping over a list by the index value?

Good question. First off, if you want to update individual values in a list you’re likely going to need to loop that includes the index values. There are functions such as enumerate() that can help, but their use can be somewhat confusing for new programmers. Second, in cases where you have multiple lists that are related to one another, it can be handy to use a loop with the index values to be able to access corresponding locations in each list. For this, let’s consider an example with the two lists below.

[22]:
cities = ['Helsinki', 'Stockholm', 'Oslo', 'Reykjavik', 'Copenhagen']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module
[23]:
countries = ['Finland', 'Sweden', 'Norway', 'Iceland', 'Denmark']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

As you can see we have 5 cities and 5 corresponding counties. Can you print out each pair using a single for loop?

[24]:
for i in range(len(cities)):
    print(cities[i], 'is the capital of', countries[i])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/opt/python/3.8.0/lib/python3.8/codeop.py in __call__(self, source, filename, symbol)
    131
    132     def __call__(self, source, filename, symbol):
--> 133         codeob = compile(source, filename, symbol, self.flags, 1)
    134         for feature in _features:
    135             if codeob.co_flags & feature.compiler_flag:

TypeError: required field "type_ignores" missing from Module

Cool. So as you can see, the index i is used in this case to access each item in the two lists of cities and countries and allow us to print out the city/country pairs. We’ll get more practice with this kind of thing in the exercises for this week.

Note: In the example above, we used the length of the list cities in the range() function. We could just as easily used the list countries to define the values of i since both lists are the same length.

Poll question pause

What output would the following program produce?

odd_numbers = [1, 3, 5, 7, 9]
even_numbers = [10, 4, 6, 8, 2]
for i in range(len(odd_numbers)):
    print(odd_numbers[i] + even_numbers[i])

Select your answer from the poll options at https://geo-python.github.io/poll/.