Have you ever had a need to iterate over two lists at the same time? I did recently and it turns out that Python has a built-in function called zip() that can be used for this. This function takes iterables as arguments, such as lists or strings, and returns a list of tuples containing elements from each of the arguments.

When using it in our code we can mix types of arguments, they don't have to be the same. In the examples below we will use lists of characters, digits, and tuples. For good measure, we will throw in a string.

word = "QWER"
letters = ['a', 'b', 'c', 'd']
digits = [1, 2, 3, 4]
tuples = [('C',12), ('D',13), ('E',14), ('F',15)]

let_dig = zip(letters, digits)
let_dig_tup = zip(letters, digits, tuples)
word_dig = zip(word, digits)

print("Lettersanddigits: {}").format(let_dig)
print("Letters, digits, and tuples: {}").format(let_dig_tup)
print("Chars in words, and digits: {}").format(word_dig)

And output:

Letters and digits: [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Letters, digits, and tuples: [('a', 1, ('C', 12)), ('b', 2, ('D', 13)), ('c', 3, ('E', 14)), ('d', 4, ('F', 15))]
Chars in words, and digits: [('Q', 1), ('W', 2), ('E', 3), ('R', 4)]

Now that we see what this function does, we will use it in a for loop to iterate over two lists simultaneously:

for let, dig in zip(letters, digits):
    print("Letter: '{}' and matching digit: {}").format(let, dig)
Letter: 'a' and matching digit: 1
Letter: 'b' and matching digit: 2
Letter: 'c' and matching digit: 3
Letter: 'd' and matching digit: 4

Perfect, just what we wanted, we iterated two lists at the same time. But what happens if our arguments have different lengths?

four_elems = [2, 4, 8, 16]
seven_elems = [0, 1, 1, 2, 3, 5, 8]

for fo_el, se_el in zip(four_elems, seven_elems):
    print("One of four elements: {}; One of seven elements: {}").format(fo_el, se_el)
One of four elements: 2; One of seven elements: 0
One of four elements: 4; One of seven elements: 1
One of four elements: 8; One of seven elements: 1
One of four elements: 16; One of seven elements: 2

The resulting list has been truncated to match the length of the shortest argument. By using the zip() function we avoided hitting the "index out of range" error. The below code illustrates how easy it's to make this mistake if we assume that both lists have the same length.

for i in xrange(len(seven_elems)):
    print("One of four elements: {}; One of seven elements: {}").format(four_elems[i], seven_elems[i])
One of four elements: 2; One of seven elements: 0
One of four elements: 4; One of seven elements: 1
One of four elements: 8; One of seven elements: 1
One of four elements: 16; One of seven elements: 2

Traceback (most recent call last):
    print("One of four elements: {}; One of seven elements: {}").format(four_elems[i], seven_elems[i])
IndexError: list index out of range

There is one more thing that zip() function can be useful for. When combined with the * operator we can use zip() to unzip a list. That is, taking a list of tuples we want to have each of the n-th elements of the tuple assigned to a separate list.

zipped=[(1,2), (4,3), (9,5), (16,7), (25,11)]

squares, primes = zip(*zipped)

print("Squares: {}").format(squares)
print("Primes: {}").format(primes)
Squares: (1, 4, 9, 16, 25)
Primes: (2, 3, 5, 7, 11)

All done, we took a list of tuples and unzipped it into separate lists.

To summarise, the function zip() is just another one of those small built-in functions that come very handy and it's definitely worth knowing how to use it.