Python Part I · Module 8 · Dictionaries
Choosing a Structure: String, List, Tuple, Dict or Set
Five modules have each ended by asking which of two structures fits. This chapter asks the question once, across all of them, because that is the decision that actually comes up — you have some data and nobody has told you what shape it is. Getting it right is worth more than remembering any method, for a reason worth stating plainly: a method you have forgotten costs you thirty seconds, and a structure chosen wrongly costs you every line of code that touches it, plus the bugs that arrive when the data changes. The good news is that three questions discriminate between all five, and you have already met every one of them.
Intermediate15 min readPythonPart IFundamentalsData StructuresDesign
Part I · Module 8 · Chapter 8.9 · Choosing a Structure: String, List, Tuple, Dict or Set
1. The Five, on One Page
one_name = "axi_smoke"
suite = ["smoke", "reset"]
job = ("axi_smoke", 4210)
record = {"test": "axi_smoke", "status": "PASS"}
failures = {"axi_burst", "reg_rw"}
print(type(one_name).__name__, type(suite).__name__, type(job).__name__,
type(record).__name__, type(failures).__name__)str list tuple dict setFive values, five types, all describing parts of the same regression. Each one is the right shape for a different question, and the rest of this chapter is about telling them apart before you write any code around them.
One detail worth noticing immediately:
one_name = "axi_smoke"
suite = ["smoke", "reset"]
job = ("axi_smoke", 4210)
record = {"test": "axi_smoke", "status": "PASS"}
failures = {"axi_burst", "reg_rw"}
print(len(one_name), len(suite), len(job), len(record), len(failures))9 2 2 2 2len(one_name) is 9, not 1. A string is a sequence of characters, so len() counts them. The other four are collections of two things each. A string is the odd one out and it is worth remembering when a name arrives where a collection was expected.
2. Three Questions
Three questions get you to any of them.
Question 1 — is this one thing, or several?
A test name is one thing. "axi_smoke" is a string, and the fact that it is made of characters is incidental — you will not ask for its fourth letter. If it is one thing, you are done.
Question 2 — if it is several, do they have names, or positions?
Named means a dictionary: the status of reset, the width of clk. Chapter 8.8 settled this one.
Positional means a list or a tuple, and that needs Question 3.
Question 3 — is the number of items fixed by what the data means?
Fixed means a tuple: a (test, seed) pair is always two things, and a third element would make it something else. Chapter 6.7 settled this one.
Not fixed means a list: a suite gains tests, a log gains lines.
And the one question that bypasses the others: if the only thing you will ever ask is "is this value present?" — with order and repeats carrying no information — it is a set. Chapter 7.6 settled that.
3. String: One Piece of Text
name = "axi_smoke"
print(name.upper())
print(name[:3])
print(len(name))AXI_SMOKE
axi
9A string is a sequence — of characters — so it indexes and slices. But you choose it because the data is one piece of text, not because you intend to work through its characters.
The mistake it prevents: treating a collection of names as one string joined together. If you find yourself splitting a string to get at items, the data was a list and something flattened it too early.
4. List: Ordered, Repeats Allowed
suite = ["smoke", "reset"]
suite.append("burst")
print(suite[0], suite[-1], len(suite))smoke burst 3Position means something, repeats are real, and the collection grows.
The question it answers: what is first, what is next, what is the whole sequence?
The question it cannot: what is the status of reset? Only by searching.
5. Tuple: a Fixed Record
job = ("axi_smoke", 4210)
test, seed = job
print(test, seed)
print(len(job))axi_smoke 4210
2Two positions whose meanings are fixed by what a job is. Chapter 6.5's unpacking is the natural way to read one.
The question it answers: what are the parts of this one thing?
The question it cannot: can I add another part? No — and that is the point, not a limitation.
Against a dictionary: a tuple's positions carry meaning only because you remember them. job[1] is the seed because you know it is. A dictionary says so. Prefer a tuple for small, universally understood pairs and when you need a key or a set member; prefer a dictionary when a reader would have to be told what position 2 means.
6. Dictionary: Key to Value
record = {"test": "axi_smoke", "status": "PASS"}
print(record["status"])
print(len(record))PASS
2The question it answers: given this name, what is the associated value?
The question it cannot: what came first? There is an insertion order, but asking for "entry zero" is not something a mapping does — Chapter 8.1 showed d[0] is KeyError: 0.
7. Set: Membership Only
failures = {"axi_burst", "reg_rw"}
print("axi_burst" in failures)
print(len(failures))True
2The question it answers: is this one of them? And, with Chapter 7.4's operations, what is in this group and not in that one?
The question it cannot: which failed first, or how many times. Both are discarded on the way in — Chapter 7.6 was about what that costs.
8. Real Data Is Usually a Combination
The question is rarely answered once. Most real structures apply it at two levels:
runs = {
"smoke": {"status": "PASS", "seeds": [1, 2]},
"reset": {"status": "FAIL", "seeds": [3]},
}
print(len(runs))
print(runs["reset"]["status"])
print(runs["smoke"]["seeds"][1])2
FAIL
2Three answers in one structure: a dictionary keyed by test name, each value a dictionary of named fields, and one of those fields a list of seeds because seeds are a sequence that grows.
Ask the three questions once per level and the shape falls out. Chapter 8.7 covers reading it.
Two combinations worth recognising because they appear constantly:
- A list of tuples —
[("smoke", "PASS"), ("reset", "FAIL")]. An ordered sequence of fixed records. Chapter 6.7 named this the most common shape in real data, and Chapter 8.5's.items()produces exactly it. - A dictionary of lists —
{"axi_burst": [48211, 48215]}. Look up by name, then work through a sequence.
9. The Decision, Collected
By the question each structure answers — not by the methods each provides:
| The question your data must answer | Structure | Written |
|---|---|---|
| What is this one piece of text? | str | "axi_smoke" |
| What is the sequence, and what is first? | list | ["smoke", "reset"] |
| What are the fixed parts of this one thing? | tuple | ("axi_smoke", 4210) |
| Given this name, what is its value? | dict | {"test": "axi_smoke"} |
| Is this one of them? | set | {"axi_burst", "reg_rw"} |
And the properties that discriminate, for when two seem to fit:
| str | list | tuple | dict | set | |
|---|---|---|---|---|---|
| Ordered | yes | yes | yes | insertion | no |
| Changeable | no | yes | no | yes | yes |
| Repeats kept | yes | yes | yes | values only | no |
| Found by | position | position | position | key | membership |
| Can be a key or set member | yes | no | if its parts can | no | no |
That last row is worth a second look: it is Chapter 7.1's hashability rule, and it is the only property that decides what can go inside a set or be used as a dictionary key.
10. Predict, Run, Observe
For each description, name the structure before reading on, and say which question decided it.
- the lines of a log file, in order
- the name of the simulator being used
- a screen coordinate
- which tests failed at least once
- the runtime of each test
- the arguments to pass to a tool, built up as options are chosen
- one test's name, seed, status and runtime together
Then this, which is about the types:
a = "clk"
b = ["clk"]
c = ("clk",)
d = {"clk": 1}
e = {"clk"}
print(len(a), len(b), len(c), len(d), len(e))
print(type(a).__name__, type(b).__name__, type(c).__name__, type(d).__name__, type(e).__name__)The verified output:
3 1 1 1 1
str list tuple dict setThe answers to the seven: list (order, grows), string (one piece of text), tuple (two fixed parts), set (membership, repeats meaningless), dictionary (by name), list (ordered, built up), dictionary (named fields of one thing).
And the code: len(a) is 3 — three characters — while every collection holding one item has length 1. The string is counting something different from the other four, which is Section 1's point in its smallest form.
11. Exercises
Exercise 1 — name the structure. For each, give the structure and the deciding question in one sentence: (a) the bit width of each signal (b) an RGB colour (c) every error code seen (d) the order of phases in a flow (e) a file path (f) one row of a results table with four named fields
Exercise 2 — predict. For x = "ab", y = ["ab"], z = ("ab",), w = {"ab": 1}, v = {"ab"}, give len() and type() for each, then explain the one that differs from the rest.
Exercise 3 — two levels. Design the structure for "for each project, which blocks it contains, and for each block its owner". Say which structure sits at each level and which question decided it.
Exercise 4 — the wrong choice. A script stores measured runtimes in a set so it can ask which values occurred. Explain in three sentences what goes wrong, which question was answered incorrectly, and what it should be.
Exercise 5 — tuple or dictionary. For a record of a test name and a seed, argue for the tuple in two sentences and for the dictionary in two sentences. Then say which you would choose if that record had to be a dictionary key, and why that settles it.
Exercise 6 — the combination. Identify the structures at every level of {"a": [("x", 1), ("y", 2)]} and say what question each level answers.
Exercise 7 — the hashability row. Explain in two sentences why a list cannot be a dictionary key or a set member while a tuple of strings can, and name the chapter that established the rule.
Exercise 8 — defend a change. A colleague wants to convert results = [("smoke", "PASS"), ("reset", "FAIL")] into a dictionary. Write three sentences: when that is clearly right, when it would lose information, and the one question you would ask before approving it.
12. Summary
- Three questions decide it. Is this one thing or several? If several, are they found by name or by position? If by position, is the count fixed by what the data means?
- One question bypasses the rest: if only membership matters, it is a set.
str— one piece of text.len()counts characters, which makes it the odd one out.list— ordered, repeats kept, grows. Answers what is first, what is the sequence.tuple— a fixed record whose positions have meaning. Answers what are the parts of this one thing.dict— key to value. Answers given this name, what is its value.set— distinct values, membership only. Answers is this one of them.- Only hashable values may be dictionary keys or set members — the single property that constrains what goes inside.
- Real data is a combination. Ask the questions once per level; a list of tuples and a dictionary of lists are the two shapes you will meet most.
- This decision outranks remembering methods. A forgotten method costs a search; a wrong structure costs every line that touches it.
That completes Module 8 — Dictionaries, and with it the collections half of Part I.
You began Module 4 able to work with one value at a time. You can now hold text and reach into it; hold an ordered collection that grows; hold a fixed record whose shape is part of its meaning; hold a group of distinct values and compare two such groups in one line; and hold a mapping from names you choose to values you store — building it, reading it safely when a field may be missing, changing it, walking it, copying it with full knowledge of what a copy does and does not protect, and nesting it when the data genuinely has levels.
The most valuable idea in this module is Chapter 8.1's, and it is worth carrying: a key is a fact about meaning, where a position is a fact about arrangement. Everything else followed from it — why d[0] is a KeyError rather than a TypeError, why reordering cannot break a lookup, why in checks keys, and why a dictionary is the structure most engineering results eventually live in.
What you still cannot do is decide. Every chapter in Part I so far has produced an answer — a boolean, a count, a status — without ever being able to act differently on it. "ERROR" in line has been available since Module 4 and you have never once been able to do one thing when it is true and another when it is false. Module 9 — Conditional Statements is that, and it is the point at which the values you have spent five modules learning to build finally start driving what a program does.
Where this fits
Part of the Python curriculum.
