for loops

Published

2023-08-02

for loops

We’ve seen that while loops are useful when we know we wish to perform a calculation or task, but we don’t know in advance how many iterations we may need. Thus, while loops provide a condition, and we loop until that condition (whatever it may be) no longer holds true.

Python has another type of loop which is useful when:

  • we know exactly how many iterations we require, or
  • we have some sequence (for example, list, tuple, or string) and we wish to perform calculations, tasks, or operations with respect to the elements of the sequence (or some subset thereof).

This new kind of loop is the for loop. for loops are so named because they iterate for each element in some iterable. Python for loops iterate over some iterable. Always.1

What’s an iterable? Something we can iterate over, of course! And what might that be? The sequence types we’ve seen so far (list, tuple, string) are sequences, and these are iterable. We can also produce other iterable objects (which we shall see soon).

Here’s an example. We can iterate over a list, [1, 2, 3], by taking the elements, one at a time, in the order they appear in sequence.

>>> numbers = [1, 2, 3]
>>> for n in numbers:
...     print(n)
...
1
2
3

See? In our for loop, Python iterated over the elements (a.k.a. “members”) of the list provided. It started with 1, then 2, then 3. At that point the list was exhausted, so the loop terminated.

If it helps, you can read for n in numbers: as “for each number, n, in the iterable called ‘numbers’.”

This works for tuples as well.

>>> letters = ('a', 'b', 'c')
>>> for letter in letters:
...     print(letter)
...
a
b
c

Notice the syntax: for <some variable> in <some iterable>:. As we iterate over some iterable, we get each member of the iterable in turn, one at a time. Accordingly, we need to assign these members (one at a time) to some variable.

In the first example, above the variable has the identifier n.

>>> numbers = [1, 2, 3]
>>> for n in numbers:
...     print(n)
...

As we iterate over numbers (a list), we get one element from the list at a time (in the order they appear in the list). So at the first iteration, n is assigned the value 1. At the second iteration, n is assigned the value 2. At the third iteration, n is assigned the value 3. After the third iteration, there are no more elements left in the sequence and the loop terminates.

Thus, the syntax of a for loop requires us to give a variable name for the variable which will hold the individual elements of the sequence. For example, we cannot do this:

>>> for [1, 2, 3]:
...     print("Hello!")

If we were to try this, we’d get a SyntaxError. The syntax that must be used is:

for <some variable> in <some iterable>:
    # body of the loop, indented

where <some variable> is replaced with a valid variable name, and <some iterable> is the name of some iterable, be it a list, tuple, string, or other iterable.

Iterating over a range of numbers

Sometimes we want to iterate over a range of numbers or we wish to iterate some fixed number of times, and Python provides us with a means to do this: the range type. This is a new type that we’ve not seen before. range objects are iterable, and we can use them in for loops.

We can create a new range object using Python’s built-in function range(). This function, also called the range constructor, is used to create range objects representing arithmetic sequences.2

Before we create a loop using a range object, let’s experiment a little. The simplest syntax for creating a range object is to pass a positive integer as an argument to the range constructor. What we get back is a range object, which is like a list of numbers. If we provide a positive integer, n, as a single argument, we get a range object with n elements.

>>> r = range(4)

Now we have a range object, named r. Let’s get nosy.

>>> len(r)
4

OK. So r has 4 elements. That checks out.

>>> r[0]
0
>>> r[1]
1
>>> r[2]
2
>>> r[3]
3
>>> r[4]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: range object index out of range

We see that the values held by this range object, are 0, 1, 2, and 3, in that order.

Now let’s use a range object in a for loop. Here’s the simplest possible example:

>>> for n in range(4):
...     print(n)

What do you think this will print?

  • The numbers 1 through 4?
  • The numbers 0 through 4? (since Python is zero-indexed)
  • The numbers 0 through 3? (since Python slices go up to, but do not include, the stop index)

Here’s the answer:

>>> for n in range(4):
...     print(n)
...
0
1
2
3

Zero through three. range(n) with a single integer argument will generate an arithmetic sequence from 0 up to, but not including, the value of the argument.

Notice, though, that if we use range(n) our loop will execute n times.

What if we wanted to iterate integers in the interval [5, 10]? How would we do that?

>>> for n in range(5, 11):
...     print(n)
...
5
6
7
8
9
10

The syntax here, when we use two arguments, is range(<start>, <stop>), where <start> and <stop> are integers or variables with integer values. The range will include integers starting at the start value up to but not including the stop value.

What if, for some reason, we wanted only even or odd values? Or what if we wanted to count by threes, or fives, or tens? Can we use a different step size or stride? Yes, of course. These are all valid arithmetic sequences. Let’s count by threes.

>>> for n in range(3, 19, 3):
...     print(n)
...
3
6
9
12
15
18

This three argument syntax is range(<start>, <stop>, <stride>). The last argument, called the stride or step size corresponds to the difference between terms in the arithmetic sequence (the default stride is 1).

Can we go backward? Yup. We just use a negative stride, and adjust the start and stop values accordingly.

>>> for n in range(18, 2, -3):
...     print(n)
... 
18
15
12
9
6
3

This yields a range which goes from 18, down to but not including 2, counting backward by threes.

So you see, range() is pretty flexible.

What if I just want to do something many times and I don’t care about the members in the sequence?

No big deal. While we do require a variable to hold each member of the sequence or other iterable we’re iterating over, we aren’t required to use it in the body of the loop. There is a convention, not required by the language, but commonly used, to use an underscore as the name for a variable that we aren’t going to use or don’t really care about.

>>> for _ in range(5):
...    print("I don't like Brussles sprouts!")
...
I don't like Brussles sprouts!
I don't like Brussles sprouts!
I don't like Brussles sprouts!
I don't like Brussles sprouts!
I don't like Brussles sprouts!

(Now you know how I feel about Brussels sprouts.)

Comprehension check

  1. What is the evaluation of sum(range(5))?

  2. What is the evaluation of max(range(10))?

  3. What is the evaluation of len(range(0, 10, 2))

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).

Footnotes

  1. for loops in Python work rather differently than they do in many other languages. Some languages use counters, and thus for loops are count-controlled. For example, in Java we might write

    for (int i = 0; i < 10; ++i) {
        // do something
    }

    In this case, there’s a counter, i, which is updated at each iteration of the loop. Here we update by incrementing i using ++i (which in Java increments i). The loop runs so long as the control condition i < 10 is true. On the last iteration, with i equal to nine, i is incremented to ten, then the condition no longer holds, and the loop exits. This is not how for loops work in Python! Python for loops always iterate over an iterable.↩︎

  2. An arithmetic sequence, is a sequence of numbers such that the difference between any number in the sequence and its predecessor is constant. 1, 2, 3, 4, is an arithmetic sequence because the difference between each of the terms is 1. Similarly, 2, 4, 6, 8, is an arithmetic sequence because the difference between each term is 2. Python range objects are restricted to arithmetic sequences of integers.↩︎