Four Ways to Walk a Dictionary
A list has two loop headers. A dictionary has four, and they all read almost identically — for k in prices, for k in prices.keys(), for v in prices.values(), for k, v in prices.items(). The only difference is what lands in the box on the left of in, and everything you can then write in the body follows from that.
1All four at once
Pick a header and step it round by round. Watch the loop variable, because the loop variable is the difference:
Pick a header, then step it round by round and watch what lands in the box on the left of in.
Nothing yet — the loop has not started.
2Form 1 — for k in prices: gives you the keys
Loop over a dictionary by its own name and each round hands you a key. Not a value, and not a pair — a key.
# looping over a dictionary gives you its keys
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item in prices:
print(item)pen notebook eraser scale
prices[item], and it is the shortest form that still gives you access to both.# the key is enough — the value is one lookup away
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item in prices:
print(item, 'costs', prices[item])pen costs 10 notebook costs 45 eraser costs 5 scale costs 20
3Form 2 — .keys() is the same thing, said out loud
# identical to the loop above, in every way
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item in prices.keys():
print(item, 'costs', prices[item])pen costs 10 notebook costs 45 eraser costs 5 scale costs 20
for k in prices: and for k in prices.keys(): do exactly the same thing. The second is longer and says what it means, which is why plenty of people prefer it — and why an exam answer that uses either is right. Neither is faster or safer than the other.4Form 3 — .values() gives you the values, and nothing else
# the values on their own
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for p in prices.values():
print(p)10 45 5 20
Use it when the keys genuinely do not matter — a total, a largest, a count:
# what does the whole basket cost?
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
total = 0
for p in prices.values():
total = total + p
print('Total:', total)Total: 80
for p in prices.values(): the name of the item is simply not available — you have the number 45 and no way to learn it belonged to the notebook. So the moment the answer has to name something, this form is the wrong one.5Form 4 — .items() hands over a tuple
.items() is the one that gives you both. What it gives you each round is a two-part tuple — the key first, the value second:
# one name in the header: each round is a whole tuple
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for pair in prices.items():
print(pair)('pen', 10)
('notebook', 45)
('eraser', 5)
('scale', 20)The brackets and the comma in the output are not decoration. That really is a tuple, so everything you know about tuples applies to it:
# it is an ordinary tuple, so index it like one
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for pair in prices.items():
print(pair[0], 'costs', pair[1])pen costs 10 notebook costs 45 eraser costs 5 scale costs 20
6Two names in the header, and the tuple unpacks itself
pair[0] and pair[1] work and read badly. Since each round hands over a two-part tuple, and unpacking takes a two-part tuple apart into two names, the loop header can do it for you — put two names where the loop variable goes:
# two names: the pair is unpacked as it arrives
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item, price in prices.items():
print(item, 'costs', price)pen costs 10 notebook costs 45 eraser costs 5 scale costs 20
('pen', 10) — the same tuple as before. Then item, price = ('pen', 10) runs, which is the plain unpacking from the Packing & Unpacking chapter, and item becomes 'pen' while price becomes 10. Two names on the left of in is a request to unpack, and it works in a loop header for the same reason it works on a line of its own.This is the form to reach for whenever the body needs both halves. It is shorter than the bare loop with its prices[item] lookup, and far easier to read than pair[0] and pair[1].
7The two mistakes, and what they look like
Mistake 1 — two names, but .items() forgotten. The loop then hands over keys, and Python tries to unpack the key into two names:
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item, price in prices:
print(item, price)Traceback (most recent call last):
File "forgot_items.py", line 3, in <module>
for item, price in prices:
^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)'pen' is a three-character string, and unpacking it into two names is one value too many. Which explains the strangest version of this bug — when the keys happen to be two characters long, nothing goes wrong at all:
# two-letter keys, so the unpacking "works" — on the letters
codes = {'IN': 91, 'US': 1, 'UK': 44}
for a, b in codes:
print(a, b)I N U S U K
'IN' was split into its two letters, and the dialling codes never appeared. If a loop over a dictionary is printing single characters, this is why — add .items().Mistake 2 — the wrong number of names. A pair is two things, so it needs exactly two names:
prices = {'pen': 10, 'notebook': 45}
for a, b, c in prices.items():
print(a, b, c)Traceback (most recent call last):
File "three_names.py", line 3, in <module>
for a, b, c in prices.items():
^^^^^^^
ValueError: not enough values to unpack (expected 3, got 2)8What .keys() and .values() actually hand back
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
print(prices.keys())
print(prices.values())
print(prices.items())dict_keys(['pen', 'notebook', 'eraser', 'scale'])
dict_values([10, 45, 5, 20])
dict_items([('pen', 10), ('notebook', 45), ('eraser', 5), ('scale', 20)])They are not lists, and printing one shows a wrapper round the outside. For a for loop, for in, for len(), sum(), max() and sorted(), this makes no difference at all. When you really need a list — to index it, or to sort it in place — ask for one:
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
print(list(prices.keys()))
print(list(prices.values()))
print(sum(prices.values()))['pen', 'notebook', 'eraser', 'scale'] [10, 45, 5, 20] 80
9The order, and how to change it
Every one of these loops walks the dictionary in insertion order — the order the keys were first put in. It is not sorted. To walk it in order of key, walk sorted() instead:
# sorted() on a dictionary sorts its KEYS
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}
for item in sorted(prices):
print(item, prices[item])eraser 5 notebook 45 pen 10 scale 20
sorted(prices) hands back a sorted list of keys, so the value still needs the lookup. That is the price of the ordering, and it is a small one.
10Recap
| Header | Each round hands over | Reach for it when |
|---|---|---|
for k in prices: | a key — 'pen' | you want the keys, or key plus a prices[k] lookup |
for k in prices.keys(): | a key — 'pen' | the same, spelled out. Identical behaviour |
for v in prices.values(): | a value — 10 | totals and counts, where the name never matters |
for pair in prices.items(): | a tuple — ('pen', 10) | rarely — the two-name form below is nearly always better |
for k, v in prices.items(): | the same tuple, unpacked into k and v | the body needs both halves. The everyday choice |
- 1
Print every key of a dictionary, one per line, three ways.
Hint · The bare name,
.keys(), andfor k, v in …items()ignoringv. All three print the same thing. - 2
From a dictionary of item prices, print only the items costing more than 20.
Hint ·
for item, price in prices.items():and oneif. You need both halves, so it has to be this form. - 3
Add up all the values without
sum().Hint ·
total = 0above the loop, andfor v in prices.values():because the keys are not needed. - 4
Print the pairs sorted by key.
Hint ·
for k in sorted(prices):, thenprices[k]for the value. - 5
Find the key with the largest value, without
max().Hint · Two champions moving together inside one
if, over.items().
What does the loop variable hold in `for x in prices:`?
Why does `for item, price in prices:` raise ValueError?
Which form should you use when the body needs both the key and the value?