Python Part I · Module 8 · Dictionaries
Key and Value: The Dictionary Mental Model
You now have four collections, and every one of them finds things the same way: by position, or by membership. A list element lives at a number. A tuple field lives at a number. A set answers only whether something is present. None of them lets you say what you actually mean when the data is a result — which test, and what happened to it. A dictionary does. It stores a value under a key you choose, and you retrieve it by naming that key rather than by counting. The syntax borrows square brackets from indexing, which makes it look familiar and is the single biggest source of confusion about it. This chapter is about the difference underneath, because everything else in the module follows from getting it right.
Foundation14 min readPythonPart IFundamentalsDictionariesMental Model
Part I · Module 8 · Chapter 8.1 · Key and Value: The Dictionary Mental Model
1. One Result, Several Named Fields
Here is the result of a single test run:
result = {"test": "axi_smoke", "seed": 4210, "status": "PASS"}
print(result["status"])PASSresult["status"] is read as "the status of result". You did not count anything and you did not need to know what order the fields were written in — you named the field you wanted.
The other two fields work the same way:
result = {"test": "axi_smoke", "seed": 4210, "status": "PASS"}
print(result["test"])
print(result["seed"])axi_smoke
4210Each name inside the braces is a key. Each thing it is paired with is a value. The whole collection is a dictionary, and its job is to answer one question: given this key, what value is associated with it?
result = {"test": "axi_smoke", "seed": 4210, "status": "PASS"}
print(type(result))
print(len(result))<class 'dict'>
3Type dict, and len() counts entries — three key-value pairs, not six items.
2. Four Layers, Kept Separate
It is worth naming the pieces precisely now, because the distinctions matter enormously by Chapter 8.6.
resultis a name. Chapter 2.3 established what that means: a name refers to a value.- The thing it refers to is a dictionary.
- Inside that dictionary,
"status"is a key. "PASS"is the value that key is mapped to.
Read the second arrow carefully: the key is what selects the value. A different key would arrive at a different value in the same dictionary.
3. A Key Is Not a Position
The square brackets are the same ones you used on strings, lists and tuples. What goes inside them is not.
A list selects by position:
tests = ["PASS", "FAIL"]
print(tests[1])FAILA dictionary selects by key:
status = {"smoke": "PASS", "reset": "FAIL"}
print(status["reset"])FAILBoth printed FAIL, and the two lines mean completely different things. tests[1] says the element in position one. status["reset"] says the value filed under "reset".
4. The Experiment That Settles It
Reorder both collections and ask the same questions again.
The list first:
tests2 = ["FAIL", "PASS"]
print(tests2[1])PASStests2[1] now gives PASS. The expression did not change; its meaning did, because position one now holds something else. If the first version was asking "did reset fail?", the second version is silently asking about a different test and answering confidently.
Now the dictionary:
status2 = {"reset": "FAIL", "smoke": "PASS"}
print(status2["reset"])FAILStill FAIL. The key names the thing you meant, so rearranging the entries cannot change what the lookup finds.
And the two dictionaries really are the same value, despite being written differently:
status = {"smoke": "PASS", "reset": "FAIL"}
status2 = {"reset": "FAIL", "smoke": "PASS"}
print(status == status2)TrueEquality compares the mapping — which keys map to which values — and not the order they were typed in. Chapter 8.2 returns to this.
5. The Lookup Produces an Ordinary Value
Nothing exotic comes back. You get the value itself, with its own type:
rec = {"name": "axi_smoke", "seed": 4210, "passed": True}
print(type(rec["name"]), type(rec["seed"]), type(rec["passed"]))<class 'str'> <class 'int'> <class 'bool'>A string, an integer and a boolean — whatever was stored. Which means a lookup is an expression like any other from Module 3, usable immediately:
rec = {"name": "axi_smoke", "seed": 4210, "passed": True}
print(rec["seed"] + 1)
print(rec["name"].upper())4211
AXI_SMOKEArithmetic on the integer, a string method on the string. No conversion, no unwrapping.
6. What d[0] Actually Does
Here is the most convincing demonstration in the chapter that the brackets mean something new.
Ask a dictionary for position 0:
r = {"a": 1, "b": 2}
print(r[0])KeyError: 0Read that error. It is not a TypeError saying numbers are unacceptable, and it is not a complaint about subscripting. It is KeyError: 0 — Python took 0 as a key, went looking for it, and did not find one.
If the dictionary happened to have a key 0, that line would have worked and returned its value. The number was never interpreted as a position at all.
Slicing is the same story:
r = {"a": 1, "b": 2}
print(r[0:1])KeyError: slice(0, 1, None)Python built a slice object, used that as the key, and failed to find it. There is no slicing concept here to reject — only keys that are present or absent.
7. What the Key Can Be
Keys are not restricted to strings. Anything that can serve as a stable label works:
widths = {"clk": 1, "addr": 32, "data": 64}
print(widths["addr"])
print(type(widths["addr"]))32
<class 'int'>Here the keys are signal names and the values are bit widths — which reads exactly as the data means. Chapter 8.2 covers what is and is not allowed as a key, building on Chapter 7.1's hashability rule.
8. Insertion Order Is Preserved
One factual point, stated carefully because Chapter 7.1 said something different about sets.
A modern Python dictionary remembers the order entries were added, and displays and iterates them in that order:
d = {}
d["b"] = 2
d["a"] = 1
print(d)
print(list(d)){'b': 2, 'a': 1}
['b', 'a']b was added first, so it comes first — not a, which would be alphabetical. That behaviour is defined and dependable, and it is genuinely different from a set, where Chapter 7.2 measured five different orders across twelve runs.
Insertion order is also not sorted order. Chapter 8.5 covers iteration properly.
9. Predict, Run, Observe
Predict all six before reading on. One raises.
result = {"test": "axi_smoke", "seed": 4210, "status": "PASS"}
print(result["test"])
print(len(result))
print(result["seed"] + 10)
print(type(result["status"]))
print(result == {"status": "PASS", "seed": 4210, "test": "axi_smoke"})
print(result["runtime"])The verified output for the first five:
axi_smoke
3
4220
<class 'str'>
Trueand the sixth:
KeyError: 'runtime'len() is 3 — three entries, not six values.
result["seed"] + 10 is 4220, because the lookup produced the integer itself.
The equality is True: same three keys mapped to the same three values, written in a different order. Order is not part of what a dictionary is for comparison purposes, even though it is remembered.
And the last line raises, because there is no "runtime" key. The error names the key it could not find — which is Chapter 8.3's whole subject.
10. Exercises
Exercise 1 — build a record. Create a dictionary describing one signal: its name, its bit width and whether it is an input. Print each field by naming its key, and print the number of entries.
Exercise 2 — key or position. For each expression, say whether it selects by position or by key, and what the brackets are asking for:
(a) tests[0] where tests is a list (b) status["smoke"] (c) name[0] where name is a string (d) widths["clk"] (e) row[1] where row is a tuple
Exercise 3 — the reordering experiment. Given pairs = ["PASS", "FAIL"] and status = {"smoke": "PASS", "reset": "FAIL"}, write down what pairs[1] and status["reset"] produce. Now rewrite both with their two entries swapped and predict both again. Explain in two sentences which one changed meaning and why.
Exercise 4 — read the error. Run {"a": 1}[0] and write down the exact message. Explain in three sentences why it is a KeyError rather than a TypeError, and what that tells you about how Python read the brackets.
Exercise 5 — the value's type. For rec = {"seed": 4210, "name": "smoke", "ok": True}, predict the result and type of rec["seed"] * 2, rec["name"][:2] and rec["ok"]. Verify all three.
Exercise 6 — equality. Predict whether each is True, then verify:
(a) {"a": 1, "b": 2} == {"b": 2, "a": 1} (b) {"a": 1} == {"a": 2} (c) {"a": 1} == {"a": 1, "b": 2}
Exercise 7 — insertion order. Write four lines that build an empty dictionary and add three entries in a deliberately non-alphabetical order, then print it. Say in one sentence what determined the printed order, and in one more why that still does not let you write d[0].
Exercise 8 — choose the shape. For each, say whether a list or a dictionary describes the data better, in one sentence each: (a) the order tests must run in (b) the status of each test (c) the lines of a log file (d) the bit width of each signal
11. Summary
- A dictionary maps keys to values and answers one question: given this key, what value is associated with it?
result["status"]reads as "the status of result" — you name the field rather than counting to it.- Four separate layers: the name refers to a dictionary; a key selects a value. Keeping them apart matters from Chapter 8.6 onwards.
len()counts entries, not keys plus values.- A key is not a position. A list selects by arrangement; a dictionary selects by meaning.
- Reordering breaks a position and not a key — the experiment in Section 4 is the whole argument for the structure.
- The lookup produces the stored value, with its own type, usable immediately as an expression.
d[0]isKeyError: 0, not aTypeError. Python treated0as a key.d[0:1]uses the slice object as a key.- Dictionaries preserve insertion order and that is dependable — unlike a set. It still does not make lookup positional.
- Equality compares the mapping, not the order entries were written in.
The next chapter builds dictionaries properly and settles what may be used as a key.
Where this fits
Part of the Python curriculum.
