Looping over multiple items

Martin McBride, 2018-03-02
Tags zip, enumerate, for loop
Categories python language, intermediate python
In section Python language

Sometimes you need to use a for loop to loop over more than one list at the same time, within the same loop. For example, suppose we have a list of employee names, and a separate list of phone extension numbers:

names = ['Anne', 'Bill', 'Carol', 'Dave']
numbers = ['234', '236', '230', '229']

What if you want to print a directory of numbers, something like:

Anne 234
Bill 236
Carol 230
Dave 229

You could do it the ugly way, with a loop index:

for i in range(4):
    print(names[i] + ' ' + numbers[i])

That's ok, it is what you might do in other languages, but Python has a better way, using the zip function.


He is how to solve the problem in a more Pythonic way:

for name, number in zip(names, numbers):
    print(name + ' ' + number)

You can probably guess what this code is doing - the two lists are being joined in some way. We then somehow loop over the list using two variables.

Most of the time you will just use and recognise this as a common idiom - you know what it does, you don't necessarily have to worry about how it works. But in fact it isn't that complicated.

How zip() works

To understand this code, we will first expand it out a bit. You should never write actual code like the code below, it is just too long-winded. Any experienced Python programmer will know how zip works in a loop. But to aid understanding we will write it longhand:

pairs = zip(names, numbers)
for pair in pairs:
    name, number = pair
    print(name + ' ' + number)

Taking the first line:

pairs = zip(names, numbers)

The zip function takes two sequnces, and creates one sequence containing tuples of the pairs of elements. So pairs will contain:

[('Anne', '234'), ('Bill', '236'), ('Carol', '230'), ('Dave', '229')]

See what has happened here?

  • the first element of pairs is a tuple created from the first element of names and the first element of numbers.
  • the second element of pairs is a tuple created from the second element of names and the second element of numbers.
  • and so on...

This is the zip operation - it joins the two lists, a bit like zipping up a coat (and nothing to do with ZIP compression!)

zip doesn't actually create a list, it creates an iterator. The values are created lazily each time the for loop asks for the next value.

Now we get to the loop:

for pair in pairs:

Since pairs is a sequence of tuples, on each pass through the loop, pair will hold a tuple value. So on the first pass through the loop, pair will hold ('Anne', '234').

The first line of the loop block looks like this:

    name, number = pair

This is simply tuple unpacking - Python shorthand for:

    name = pair[0]
    number = pair[1]

So looking at the first three lines of our example code:

pairs = zip(names, numbers)
for pair in pairs:
    name, number = pair

All we have really done is removed the unnecessary variables pairs and pair, and compressed the three lines into one:

for name, number in zip(names, numbers):

You don't need to go through all this work every time you use zip, just remember that you can zip two (or more) sequences together, and then read them into separate variables, and use it to loop through both sequences at the same time.

More about zip()

zip works with any sequence or iterable.

You can zip any number of sequences. If you zip three sequences, for example, each tuple in the result will have three elements.

If the input sequences have different lengths, the output will be the same length as the shortest input sequence. The extra elements at the end of the longer sequence(s) will be ignored.

Accessing the loop count using enumerate

Even though it is often best to avoid loop counters, there are times when they can be useful. Fortunately you don't have to forget everything you have learnt so far - you can use the enumerate function.

A common use of enumerate is if you want to loop through a list modifying the elements in place (that is, you don't want to create a new array). For example, suppose we get a new office telephone system, and each phone extension now need to start with an extra zero. How would we update our numbers list? Here is how we could use enumerate:

numbers = ['234', '236', '230', '229']

for i, number in enumerate(numbers):
    numbers[i] = '0' + number


As you might have guessed, enumerate work a little like zip, except that it only takes a single sequence as input. It creates a sequence of tuples where the first element is the count value. So for the numbers list, enumerate creates this sequence of tuples:

(0, '234'), (1, '236'), (2, '230'), (3, '229')

Use enumerate sparingly. For example, if you wanted to create the updated numbers in a new list (let's call it new_numbers), you could do it like this:

numbers = ['234', '236', '230', '229']
new_numbers = []

for number in numbers:
    new_numbers.append('0' + number)

Or you could use a list comprehension to do it in one line.

In summary, if you need to loop over two or more sequences in parallel, consider using zip() to avoid the need for a loop counter. If you really need a loop counter, for example to update the existing element of a list, consider using enumerate().

If you found this article useful, you might be interested in the book Functional Programming in Python, or other books, by the same author.


Copyright (c) Axlesoft Ltd 2020