LambdaLabTM
Computer Science · Class 11 · Dictionary Revisited
DictionariesTraversal⏱️ 16 min read

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:

🗝️ One dictionary, four loops

Pick a header, then step it round by round and watch what lands in the box on the left of in.

prices
'pen': 10
'notebook': 45
'eraser': 5
'scale': 20
for k in prices:
print(k, prices[k])
This round, the loop variable holds

Nothing yet — the loop has not started.

Output
 
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.

walk_bare.py
# looping over a dictionary gives you its keys

prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}

for item in prices:
    print(item)
Output
pen
notebook
eraser
scale
Watch Out
This is the one people guess wrong. A dictionary holds pairs, so a loop over it “should” hand over pairs. It does not — it hands over keys. There is a reason: with the key in hand you can always fetch the value with prices[item], and it is the shortest form that still gives you access to both.
walk_bare_value.py
# 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])
Output
pen costs 10
notebook costs 45
eraser costs 5
scale costs 20

3Form 2 — .keys() is the same thing, said out loud

walk_keys.py
# 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])
Output
pen costs 10
notebook costs 45
eraser costs 5
scale costs 20
Key Takeaway
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

walk_values.py
# the values on their own

prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}

for p in prices.values():
    print(p)
Output
10
45
5
20

Use it when the keys genuinely do not matter — a total, a largest, a count:

walk_values_total.py
# 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)
Output
Total: 80
Watch Out
There is no way back from a value to its key. Inside 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:

walk_items_one.py
# 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)
Output
('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:

walk_items_parts.py
# 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])
Output
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:

walk_items_two.py
# 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)
Output
pen costs 10
notebook costs 45
eraser costs 5
scale costs 20
Key Takeaway
Nothing new is happening here. The loop still hands over ('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].

dearest.py

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:

forgot_items.py
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}

for item, price in prices:
    print(item, price)
Output
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:

forgot_items_silent.py
# 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)
Output
I N
U S
U K
Watch Out
No error, and completely wrong. The key '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:

three_names.py
prices = {'pen': 10, 'notebook': 45}

for a, b, c in prices.items():
    print(a, b, c)
Output
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

views.py
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}

print(prices.keys())
print(prices.values())
print(prices.items())
Output
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:

to_list.py
prices = {'pen': 10, 'notebook': 45, 'eraser': 5, 'scale': 20}

print(list(prices.keys()))
print(list(prices.values()))
print(sum(prices.values()))
Output
['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_walk.py
# 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])
Output
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

HeaderEach round hands overReach 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 — 10totals 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 vthe body needs both halves. The everyday choice
✍️ Now write these yourself
  1. 1

    Print every key of a dictionary, one per line, three ways.

    Hint · The bare name, .keys(), and for k, v in …items() ignoring v. All three print the same thing.

  2. 2

    From a dictionary of item prices, print only the items costing more than 20.

    Hint · for item, price in prices.items(): and one if. You need both halves, so it has to be this form.

  3. 3

    Add up all the values without sum().

    Hint · total = 0 above the loop, and for v in prices.values(): because the keys are not needed.

  4. 4

    Print the pairs sorted by key.

    Hint · for k in sorted(prices):, then prices[k] for the value.

  5. 5

    Find the key with the largest value, without max().

    Hint · Two champions moving together inside one if, over .items().

Quick Check

What does the loop variable hold in `for x in prices:`?

Quick Check

Why does `for item, price in prices:` raise ValueError?

Quick Check

Which form should you use when the body needs both the key and the value?