zip() Parallel Iteration

Concepts covered: Parallel Iteration

Data often arrives column by column: a list of user IDs, a list of countries and a list of play counts, where position 2 in each list describes the same user. Reading a user means taking the same position from every list. With enumerate that is for i, user in enumerate(users): plus countries[i] and plays[i] in the body, which works and puts an index on every line that reads a column.

zip(users, countries) walks the lists in lockstep. Its first step pairs the first item of each list, its second step the second items, and so on, so for user, country in zip(users, countries): binds 1 name per list and needs no index. It accepts any number of lists, and the count of names after for has to match the count of lists passed in.

names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]

for name, age in zip(names, ages):
    print(name + " is " + str(age) + " years old")
Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old
users = ["u7", "u12", "u31"]
countries = ["BR", "SE", "JP"]
plays = [140, 85, 302]
for user, country, count in zip(users, countries, plays):
    print(user, country, count)

Output:

u7 BR 140
u12 SE 85
u31 JP 302
Counter plus indexes
  • One list drives the loop and the rest are read by position
  • Every column read is a [i] that can name the wrong list
  • A short list fails with an IndexError partway through
zip
  • All lists advance together, 1 item each per step
  • Each column gets its own name in the header
  • A short list ends the loop early and raises nothing

The shortest list decides

zip stops the moment any of its lists runs out. It raises no error and prints no warning, and the leftover items in the longer lists are never visited.

names = ["Alice", "Bob", "Charlie", "Diana"]
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(name + ":", score)

print()

letters = ["a", "b"]
numbers = [1, 2, 3, 4, 5]
for letter, num in zip(letters, numbers):
    print(letter, num)
Alice: 85
Bob: 92
Charlie: 78

a 1
b 2

In a pipeline, that silence is the risk. Suppose the country lookup failed for the last user and countries came back 1 item short. A loop over zip(users, countries, plays) writes 2 rows where there should be 3, and nothing in the output shows that u31 went missing.

Positionuserscountriesplayszip step
0u7BR1401
1u12SE852
2u31missing302never reached

Positions and parallel values together

When the row number matters too, wrap the zip in enumerate: for i, (user, count) in enumerate(zip(users, plays)):. Each step of enumerate delivers a position and an item, and here the item is itself a pair made by zip. The parentheses copy that shape: i takes the position and (user, count) splits the inner pair. Without them, Python sees 3 names for a value with 2 parts and stops with ValueError: not enough values to unpack (expected 3, got 2).

users = ["u40", "u41", "u42"]
plays = [12, 0, 9]
for i, (user, count) in enumerate(zip(users, plays), start=1):
    if count == 0:
        print("row", i, "user", user, "has no plays")

Output:

row 2 user u41 has no plays

Turning 2 lists into a dict

A frequent job for zip is building a lookup from 2 columns. The first list supplies the keys, the second supplies the values, and the loop assigns each pair.

users = ["u7", "u12", "u31"]
plays = [140, 85, 302]
plays_by_user = {}
for user, count in zip(users, plays):
    plays_by_user[user] = count
print(plays_by_user)
print(plays_by_user["u12"])

Output:

{'u7': 140, 'u12': 85, 'u31': 302}
85

If a key appears twice in the first list, the later assignment overwrites the earlier one, and the dict ends up with fewer entries than the lists have items.

The names list has 4 items and the scores list has 3. What does this program print?

names = ["ana", "ben", "cal", "dee"]
scores = [3, 9, 4]
total = 0
for name, score in zip(names, scores):
    total += score
print(total)

Building a lookup with zip takes 3 pieces: an empty dict created before the loop, a header with 1 name per list, and a body that assigns the second name's value under the first name as the key.

About this lesson

This section is part of the Loops: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.