Computers are very fast -- they can execute billions of instructions per second! How is a programmer ever going to write enough code to keep a computer busy for even 1 second?
An easy answer to that is to have the computer do the same instructions many times. Functions are one way for us to write code that we can then tell the computer to do again by calling it repeatedly. In this topic, we will see another way using iteration.
With iteration, we write “loops” where the instructions inside the loop are done repeatedly. This could be done to print a statement for every bank customer, to display new Instagram posts, or to layout boxes in a Zoom window, among many other things.
We'll start by focusing on an iteration construct called the for loop and see how we can use it to iterate over sequences.
The for loop construct allows the programmer to dynamically repeat code. With a for loop, we can easily write code that loops over sequential data, such as the characters in a string, or the numbers in a range of numbers, operating on each of those values individually.
for <variable> in <sequence> :
# any code indented one level executes for each item
# and can use the variable to access the current item in the sequence
Let's dive in with some sample code! Running this code:
def printChars(s:str) -> None:
'''Prints the characters that are in s, one per line.'''
# for every letter in s
for letter in s:
# print the current letter (on a different line)
print( letter )
printChars ("Mary Lyon")
outputs
M
a
r
y
L
y
o
n
How does this work? The identifier letter creates a reference called the loop variable, or the control variable. Python automatically updates this variable each time through the loop. When we first encounter the for loop, letter will be set to the first character in s. Then the body of the for loop is executed. In this case, it will print that letter. Then, control returns back to the for line. If there are more letters in s, letter will be set to the second letter in s, and that letter will be printed. This will continue until all of the letters have been printed. At that point, the for loop ends and control flows to the statement following the for loop. In this example, the for loop is the last statement in the function, so the entire function will end, and control will return to where the function was called.
Notice that we never explicitly assign to letter. Python does that for us. While we could assign to letter within the loop, this is bad style and you should not do that. Even if you do change letter, Python will set letter to the next character in the string anyway.
What do you think would happen if s was the empty string? Try invoking printChars and pass in the empty string to see if you are right.
In Python, we can use a function called range to iterate over a sequence integer values in a range.
If 2 parameters are provided, as in range(lo, hi), the range function will give numbers inclusive of the first parameter and exclusive (up to, but not including) of the second. For example range( 1, 5 ) will give back 1,2,3,4.
If only 1 parameter is provided, as in range(hi), the range function will give numbers starting at 0 up to, but not including the parameter. For example range( 5 ) will give back 0,1,2,3,4.
For example, running this code:
def printNums(smallest:int, largest:int) -> None:
''' Prints the integers between smallest and largest, inclusive,
one per line.'''
# for every number between smallest and largest
for num in range (smallest, largest + 1) :
# print the current number (on a different line)
print( num )
printNums(-3, 4)
outputs
-3
-2
-1
0
1
2
3
4
How does this work? The first time that the for loop is reached, num will be assigned the value smallest. It will then print that number. Then control will go back to the for line. If there are more values in the range, num will be set to the next integer, and that number will be printed. This looping will continue until largest has been printed. After largest is printed, the loop (and function) will end.
What do you think will happen if printNums is called passing the same value for both parameters? Add that call to the code above and find out!
What do you think will happen if the largest value is smaller than the largest value? Try it!
What if one or both numbers are not integers? Try it!
Here's another example. Running this code:
# greet the user 4 times
for i in range( 4 ):
print("Hello?")
outputs
Hello?
Hello?
Hello?
Hello?
Notice that in this example we never actually use the value that is in the variable i. We are just using i to execute the body of the loop 4 times.
A common thing for us to do with loops is to walk a sequence and “accumulate” some information as we do that. This pattern relies on a variable to accumulate into, initialized prior to the loop. Each iteration then contributes a value into that variable.
runningTotal:<type> = <initial_value> # identity for the accumulating operator
for <variable> in <sequence> :
# accumulate into the total
runningTotal = runningTotal <operator> <current_value>
Because accumulation is so common, there are shortcut operators to update the value of a variable by accumulating into it. The syntax is:
<variable> <operator>= <additional_value>
This is a shortcut for
<variable> = <variable> <operator> <additional_value>
For example, we can add 1 to a variable ("increment" it):
x += 1
which is equivalent to
x = x + 1
# accumulate into this variable
# initialize it to the 0 for addition
runningTotal:int = 0 # additive identity value
# for each value in a specified range
# here: 2,3,4,5
for i in range( 2, 6 ):
# accumulate the current value into the total
runningTotal += i
print( f"After the loop, runningTotal is {runningTotal}" )
# accumulate into this variable
# initialize it to the 1 for multiplication
runningTotal:int = 1 # multiplicative identity value
# for each value in a specified range
# here: 2,3,4,5
for i in range( 2, 6 ):
# accumulate the current value into the total
runningTotal *= i
print( f"After the loop, runningTotal is {runningTotal}" )
# accumulate into this variable
# initialize it to the "" for building up a string
runningTotal:str = "" # start with the empty string
# for each letter in an existing string
for letter in "MHC":
# accumulate into the total
runningTotal = runningTotal + letter*2
print( f"After the loop, runningTotal is {runningTotal}" )
This code is a sneak preview of one way to iterate over lists!
colleges:list = ["Amherst College", "Hampshire College", "Mount Holyoke College", "Smith College", "UMass Amherst"]
# accumulate into this variable
# initialize it to the [] for building up a list
collegeGreetings:list = [] # start with the empty list
# for each value in a range
for college in colleges:
# accumulate into the total by appending to the end
collegeGreetings.append( "Hello, " + college + "!")
# print out the values in the list
for greeting in collegeGreetings:
print( greeting )
It is very common for us to write code that accesses each element in a list. The for loop is very good for this purpose.
greetings:list = ["hello", "bonjour", "g'day"]
for greeting in greetings :
print(greeting + ", Mary Lyon!")
The first time through the for loop, greeting is set to the first value in the greetings list, in this case "hello", and we see "hello, Mary Lyon!". On the second iteration greeting is set to the next value in the list, "bonjour" and "bonjour Mary Lyon!" is printed. The for loop ends after each value in the list has been used within the loop.
Here is some code that adds together the values in a list. Read it carefully to see that you understand how it works. Run it in Python Tutor to check your understanding.
# Initialize the list
myList:list = [1,4,6,9]
# keep track of the sum
sum:int = 0
# for each element in my list
for elt in myList:
# add the element to the sum
sum = sum + elt
# print out the sum
print( sum )
We can also iterate through a list using an index. We would do that if we wanted to know not just the value in the list, but also its position. This is particularly handy if we want to be able to update the values in the list during the loop or access another list that we're "walking" in parallel. In that case, the for loop would look like this:
numbers:list = [1, 2, 3, 4, 5]
for i in range (len (numbers)) :
numbers[i] = numbers[i] * -1
When this loop is complete, all of the numbers will have been negated, so the list contains [-1, -2, -3, -4, -5].
Let’s look a little more closely about how the range function works so that we get exactly the values we need for i to access each element in the list. Recall that when the range function is called with a single argument, we get a range that starts at 0 and ends at one less than the value passed as the argument. In this case, we would get a range that consists of the values 0 through 4 (the upper bound 5 is not included). Conveniently, those are exactly the valid indices for our list. It turns out that this is a very common way to use the range function, and that may help explain why the argument we pass to the range function is not included in the list that the range function produces!
Take a look at the following example. Predict what you believe will be printed. Then run the code to see if you are correct. Use Python Tutor to check.
myList:list = [1,4,6,9]
# for each spot/index of my list
for indx in range(0, len(myList)):
# add one to the element at that index
myList[indx] += 1
# print out the list
print( myList )
Or we can do something like this:
numbers:list = [1, 2, 0, 3]
fruit:list = ["kiwi", "apple", "banana", "pear" ]
for i in range(len(numbers)):
print( fruit[i]*numbers[i] )
which prints
kiwi
appleapple
pearpearpear