Python Part I · Module 8 · Dictionaries
Looping Over keys, values and items
A results table is only useful if you can go through it. You might want every test name, or every outcome, or — most often — both halves together so you can print a line per result. Python offers all three, and the choice between them is genuinely a choice about the question you are asking rather than a style preference. This chapter also clears up something that trips people up the first time they look closely: the three methods do not hand you lists, and what they do hand you behaves in a way that is worth understanding before you are surprised by it.
Foundation14 min readPythonPart IFundamentalsDictionariesIteration
Part I · Module 8 · Chapter 8.5 · Looping Over keys, values and items
1. A Plain Loop Gives You the Keys
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
for name in results:
print(name)smoke
reset
crcThe loop syntax is Chapter 5.4's, unchanged — one pass per entry, the loop variable referring to one thing at a time.
What it refers to is the key. Not the value, and not a pair. That is worth noticing because it is a choice Python made, and it follows from Chapter 8.1's model: a dictionary is organised by key, so the keys are what it naturally hands out.
The proof that plain iteration yields keys is what happens if you assume otherwise:
d = {"a": 1}
for k, v in d:
print(k, v)ValueError: not enough values to unpack (expected 2, got 1)That is Chapter 6.5's unpacking error. The loop handed over the key "a" — a one-character string — and the header asked for two values out of it. There was only one character, so the unpacking failed. Had the loop been giving pairs, this would have worked; it is not.
2. keys(): Which Subjects Are Present?
The explicit form says the same thing in words:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
for name in results.keys():
print(name)smoke
reset
crcIdentical output. for name in results: and for name in results.keys(): do the same thing, and the second is worth writing when the surrounding code also uses .values() or .items() — the symmetry makes the intent obvious at a glance.
The question it answers is which test names do I have a result for?
3. values(): Which Outcomes Are Present?
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
for status in results.values():
print(status)PASS
FAIL
PASSThree outcomes, with "PASS" appearing twice — Chapter 8.2's point that values need not be unique, visible here.
The question is what outcomes occurred? Note that it has lost track of which test produced which. If you need that, you want the next section.
4. items(): Which Goes With Which?
This is the one you will use most:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
for name, status in results.items():
print(name, status)smoke PASS
reset FAIL
crc PASSOne line per result, with both halves available in the body. That is the summary the canonical curriculum names for this chapter.
Two names in the loop header is new, and it is not special dictionary syntax. Each thing items() hands over is a tuple:
results = {"smoke": "PASS", "reset": "FAIL"}
for pair in results.items():
print(pair, type(pair))('smoke', 'PASS') <class 'tuple'>
('reset', 'FAIL') <class 'tuple'>A (key, value) tuple per entry. So for name, status in results.items(): is Chapter 6.5's tuple unpacking, applied once per pass: the loop produces a two-element tuple, and the two names on the left take its two parts by position.
Compare the alternative, which uses only Chapter 8.1:
results = {"smoke": "PASS", "reset": "FAIL"}
for name in results:
print(name, results[name])smoke PASS
reset FAILThat works and does a lookup on every pass to recover something items() would have handed you. For a short loop the difference is cosmetic; items() is the clearer statement of intent.
5. What These Methods Actually Return
Here is the detail that surprises people. .keys() does not return a list:
results = {"smoke": "PASS", "reset": "FAIL"}
print(results.keys())
print(results.values())
print(results.items())
print(type(results.keys()))dict_keys(['smoke', 'reset'])
dict_values(['PASS', 'FAIL'])
dict_items([('smoke', 'PASS'), ('reset', 'FAIL')])
<class 'dict_keys'>The type is dict_keys, and it displays as dict_keys([...]) rather than as a bare list. These are called view objects: they provide access to the dictionary's keys, values or pairs without being separate collections.
They support what you would expect:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
print(len(results.keys()), len(results.values()), len(results.items()))
print("smoke" in results.keys())
print("PASS" in results.values())
print(("smoke", "PASS") in results.items())3 3 3
True
True
TrueLength, membership and iteration all work. What they are not is lists, so they have no [0] and no .append().
A view is live
This is the property that makes "view" the right word:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
ks = results.keys()
print(len(ks))
results["new"] = "PASS"
print(len(ks))3
4ks was not rebuilt and nothing was reassigned — yet its length changed, because it is a window onto the dictionary rather than a snapshot of it. Add an entry and the view shows it.
6. The Order You Get
Iteration follows insertion order, the property Chapter 8.1 established:
d = {}
d["b"] = 1
d["a"] = 2
print(list(d))['b', 'a']b first, because it was added first. That is dependable — genuinely unlike a set, where Chapter 7.2 measured five different orders across twelve runs.
Insertion order is not alphabetical order. When you want names sorted, ask for it:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
print(sorted(results))
print(sorted(results.keys()))['crc', 'reset', 'smoke']
['crc', 'reset', 'smoke']sorted() on a dictionary sorts its keys and returns a list — which also means for name in sorted(results): walks the table alphabetically, the natural way to produce a report that reads the same whatever order the results arrived in.
And recall Chapter 8.4's finding: replacing an existing key's value does not move it. A key keeps the position it had when it was first inserted, so re-assigning a result does not reshuffle your report.
7. Choosing Between Them
Now that each has arrived with a question attached:
| The question you are asking | Use |
|---|---|
| Which subjects do I have entries for? | for k in d: or d.keys() |
| What outcomes occurred, regardless of which test? | d.values() |
| Which subject goes with which outcome? | d.items() |
items() is the common case for reporting. values() is for questions about outcomes alone — counting how many passed, say. keys() is for questions about the subjects.
8. Counting, Without a Counter
A small example of the body doing real work, using only what you have:
results = {"smoke": "PASS", "reset": "FAIL", "crc": "PASS"}
total = 0
for status in results.values():
total += 1
print(total)3That counts every outcome — which len(results) would have told you more directly. Counting only some of them, such as just the failures, needs a way to decide per entry, and deciding is the if statement in Module 9. Module 10 then covers counting patterns properly.
What this chapter establishes is the walk itself: one pass per entry, both halves available, in a known order.
9. Do Not Change the Dictionary While Looping
The rule from Chapters 5.4 and 7.2, and Python enforces it here:
d = {"a": 1, "b": 2}
for k in d:
d["c"] = 3RuntimeError: dictionary changed size during iterationDeleting is the same:
d = {"a": 1, "b": 2}
for k in d:
del d[k]RuntimeError: dictionary changed size during iteration10. Predict, Run, Observe
Predict every line before reading on.
d = {"b": 2, "a": 1, "c": 3}
for k in d:
print(k)
print(list(d.values()))
print(sorted(d))
print(len(d.items()))
print(("a", 1) in d.items())
print(type(d.keys()))The verified output:
b
a
c
[2, 1, 3]
['a', 'b', 'c']
3
True
<class 'dict_keys'>The loop prints b, a, c — insertion order, which here is the order written in the literal, not alphabetical.
list(d.values()) is [2, 1, 3], matching that same order.
sorted(d) is alphabetical and returns a list of keys.
("a", 1) in d.items() is True — that exact pair is an entry. Note that ("a", 2) would be False: items membership checks the pair, not just the key.
And type(d.keys()) is dict_keys, not list.
11. Exercises
Exercise 1 — three walks. For widths = {"clk": 1, "addr": 32, "data": 64}, write the loop that prints every signal name, the loop that prints every width, and the loop that prints name width on one line each.
Exercise 2 — predict. For d = {"x": 10, "y": 20}, give each result and verify:
(a) list(d) (b) list(d.values()) (c) list(d.items()) (d) len(d.items()) (e) "x" in d.keys() (f) 10 in d.values() (g) ("x", 10) in d.items()
Exercise 3 — which view. For each question, name the right method and write the loop header:
(a) how many distinct outcomes occurred
(b) print a line per test with its result
(c) list the tests that have results at all
(d) check whether any test produced "FAIL"
Exercise 4 — the unpacking error. Run for k, v in {"ab": 1}: and write down the exact message. Explain in three sentences what the loop actually handed over, why two names could not take it, and what happens if you try the same with a key of length two.
Exercise 5 — views are not lists. Predict what each does, verify, and explain the two that fail:
(a) d.keys()[0] (b) list(d.keys())[0] (c) d.keys().append("z") (d) len(d.keys())
Exercise 6 — the live view. Predict both numbers, verify, and explain in two sentences:
d = {"a": 1}
v = d.values()
print(len(v))
d["b"] = 2
print(len(v))Exercise 7 — order. Build a dictionary by adding three keys in non-alphabetical order, then print list(d) and sorted(d). Say in one sentence what determines each, and which one you would use for a report.
Exercise 8 — the iteration rule. Explain in two sentences why for k in d: del d[k] raises, name the exact error, and say what you would do instead.
12. Summary
- A plain
forover a dictionary gives the keys — not values, not pairs.for k, v in d:fails with an unpacking error, which is the proof. .keys()asks which subjects are present,.values()which outcomes occurred,.items()which goes with which.items()is the common case for a per-entry summary.- Each item is a
(key, value)tuple, sofor name, status in d.items():is Chapter 6.5's unpacking — not special syntax. - These methods return views, not lists.
type(d.keys())isdict_keys; views are not indexable and have noappend. - A view is live — adding an entry changes its length without reassigning anything.
- Use
list(...)when you genuinely need a list, such as to keep a snapshot or to index it. - Iteration follows insertion order, dependably — and replacing a value does not move its key.
sorted(d)sorts the keys and returns a list, which is how you produce a report in a stable, readable order.- Do not add or remove entries while looping —
RuntimeError: dictionary changed size during iteration. Changing an existing key's value is fine.
You can read, change and walk a dictionary. The next chapter is about what happens when two names refer to one.
Where this fits
Part of the Python curriculum.
