Skip to content
Theory · Tuples and unpacking · lesson 5 of 8

enumerate and zip

Picture a theater cloakroom. The attendant takes each coat and hands out a numbered tag for it: number 1 for the first coat, number 2 for the second. Each visitor ends up with a pair: a tag and their coat.

Programs need such pairs all the time. The loop for name in names gives the items of a list, but not their numbers. You have to keep the number in a separate variable or go through the indices with range(len(names)). The enumerate function works like the attendant and gives the number together with the item:

Python
names = ["ann", "bob", "cid"]
for i, name in enumerate(names, 1):
    print(f"{i}.{name}", end=" ")  # 1.ann 2.bob 3.cid

Let's go through the loop header:

  1. enumerate(names, 1) gives the pairs (1, 'ann'), (2, 'bob') and (3, 'cid').
  2. for i, name in unpacks each pair: the number goes to i, the name to name.
  3. The loop body prints the number and the name with a dot between them.

Pairs from enumerate

On each iteration, enumerate gives a tuple of two items: the number and the item. What to know about these pairs:

  • the header for i, name in unpacks the tuple into two names, like an ordinary assignment;
  • the second argument sets the first number, and without it counting starts at 0, like indices;
  • enumerate accepts any iterable: enumerate("ab") gives the pairs (0, 'a') and (1, 'b').

print(enumerate(names)) does not show the pairs but prints a string like <enumerate object at 0x…>. This is where the cloakroom analogy stops working. The attendant hands out all the tags at once. enumerate gives the pairs one at a time, only when the loop asks for them.

The list() function collects all the pairs into a list that you can print and index:

Python
numbered = list(enumerate(names))
print(numbered)  # [(0, 'ann'), (1, 'bob'), (2, 'cid')]
print(numbered[-1][0])  # 2

numbered[-1] is the last tuple of the list, (2, 'cid'), and [0] takes the number from it.

zip

The zip function is named after a zipper. When you close a zipper, the teeth of its two halves lock in pairs: the first with the first, the second with the second. In the same way, zip goes through several sequences at once. At each step, it gives a tuple of their items at the same index:

Python
scores = [90, 75]
pairs = list(zip(names, scores))
print(pairs)  # [('ann', 90), ('bob', 75)]
for name, score in zip(names, scores):
    print(name, score, end="; ")  # ann 90; bob 75;

What happens in the example:

  • zip(names, scores) locks each name with the score at the same index;
  • list() collects the pairs into a list, as with enumerate;
  • the loop for name, score in unpacks each pair into two names.

The two halves of a real zipper have the same length, but lists do not have to. zip stops when the shortest sequence runs out. There is no error: "cid" has no score, and that name does not get into the pairs.

Interviewers often ask about this: "What does zip do with lists of different lengths?" The answer: it silently drops the extra items. If such a loss means an error in the data, compare the lengths before the loop: len(names) == len(scores). Another way is zip(names, scores, strict=True): then Python reports a ValueError when the lengths differ.

zip also accepts three sequences or more: then each tuple holds one item from each. A ready list of pairs is looped over with unpacking too: for name, score in pairs: gives the same names and scores as the zip loop.

Common mistake

Two sequences are listed in the for header separated by a comma, without zip:

Python
names = ["ann", "bob", "cid"]
scores = [90, 75, 60]
for name, score in names, scores:  # ✗
    print(name, score)
TEXT
ValueError: too many values to unpack (expected 2)

names, scores is a tuple of two lists, and the loop goes through the lists, not their items. On the first iteration, it tries to unpack the whole names list into two names, and that list has three items.

If each list had two items, there would be no error. But then name and score would get two names first, then two scores, not a name with its score. The correct header is for name, score in zip(names, scores):.

Key points

  • enumerate gives "number, item" pairs; the second argument sets the first number, 0 by default.
  • zip locks the items of several sequences at the same index into tuples.
  • zip stops at the shortest sequence without an error; strict=True turns a length mismatch into a ValueError.
  • Pairs from enumerate and zip are spread over names right in the for header.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way