The problem with range(len(...))
To get both the position and the item, you can loop over indexes: for i in range(len(meals)) and then read meals[i]. It works, but Python has a shorter way to get both.
enumerate: items with a count
enumerate(lst) gives each item of lst together with a count. Each step produces a pair, and the loop unpacks it into two names, as you do with tuples.
meals = ["soup", "pasta"]
for i, meal in enumerate(meals):
print(i, meal)
0 soup
1 pasta
The count starts at 0. To start at 1, write enumerate(meals, start=1), which is how people read numbered lists.
You can use enumerate on any sequence, strings included: for i, ch in enumerate("hi") gives 0 h, then 1 i. It is also the usual way to find where something is. For example, a loop with for i, s in enumerate(scores) and an if s == 15 can print the index 2 of the score 15 in [12, 9, 15], with no range(len(...)) in sight.
zip: two lists side by side
zip(a, b) pairs the first item of a with the first item of b, then the second items, and so on. Each pair is a tuple.
fruits = ["apple", "kiwi", "plum"]
prices = [3, 1, 2]
for fruit, price in zip(fruits, prices):
print(fruit, price)
apple 3
kiwi 1
plum 2
zip stops at the end of the shorter list. The extra items are skipped without an error: list(zip([1, 2, 3], "ab")) is [(1, 'a'), (2, 'b')].
The two lists should match in length. When they don't, zip doesn't warn you: list(zip(["x", "y", "z"], [1, 2])) is [('x', 1), ('y', 2)], and the third name quietly vanishes. If you meet a result that is shorter than you expected, check the lengths first.
Zipped pairs are useful outside loops too: dict(zip(["a", "b"], [1, 2])) builds the dictionary {'a': 1, 'b': 2}, with the first list as the keys.
Using both together
enumerate(zip(fruits, prices), start=1) gives a count and a pair on every step, such as (1, ('apple', 3)). Put parentheses around the pair's two names when you unpack it:
for number, (fruit, price) in enumerate(zip(fruits, prices), start=1):
print(f"{number}. {fruit} costs {price}")
1. apple costs 3
2. kiwi costs 1
3. plum costs 2
Without the parentheses, for number, fruit, price in ... stops with ValueError: not enough values to unpack (expected 3, got 2). Each item has two parts, a number and a pair, not three.
Reading the loop header
The names in a for line must match the shape of what the iterator produces. enumerate(players) produces a pair, so two names fit. zip(a, b) produces a pair, so two names fit. enumerate(zip(a, b)) produces a number and a pair, so you write two names, with the second one a pair in parentheses. When a loop fails with a "values to unpack" error, count the parts the iterator gives and compare them with the names you wrote.
The exercise, step by step
A quiz show keeps its players in players and their points in scores, in the same order.
- Loop over
enumerate(players, start=1)with the namesnumberandplayer. - Inside, print
number, a dot, a space andplayer, like1. Ana, with an f-string. Withoutstart=1the first line would be0. Ana. - Loop over
zip(players, scores)with the namesplayerandpoints. - Inside, print a line like
Ana has 12 points. Each pass sees one player and that player's points, so no index is needed.
players = ["Ana", "Ben", "Cleo"]
scores = [12, 9, 15]
for number, player in enumerate(players, start=1):
print(f"{number}. {player}")
for player, points in zip(players, scores):
print(f"{player} has {points} points")
1. Ana
2. Ben
3. Cleo
Ana has 12 points
Ben has 9 points
Cleo has 15 points
Key takeaways
enumerate(lst, start=1)gives a count and an item; the count starts at 0 unless you setstart.zip(a, b)gives pairs, one item from each list, and stops at the shorter list.- To use both, write
for number, (x, y) in enumerate(zip(a, b), start=1), keeping the parentheses.
