score_of = {'Ama': 55, 'Ben': 65, 'Chen': 80, 'Dina': 40, 'Eli': 60}
print(score_of['Chen'], len(score_of))80 5
Before this lesson
P01.6.2 Tuples, unpacking and looping in pairs P01.5 Modules, packages and environments
After this lesson you can
In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.
With cut-offs 70 and 50 the five scores fall into the bands pass, pass, distinction, fail, pass. How many of each? And how would you find Chen’s score by her name, without remembering that she sits at position 2? A list answers “what is at position 2?”. This lesson’s containers answer “what goes with the word 'Chen'?”, the same question a language model asks when it turns every word into a number.
From the last lesson
1. What does name, hours, score = ('Ama', 2, 55) do?
It unpacks the tuple: name is bound to 'Ama', hours to 2 and score to 55.
2. What does zip(hours, scores) do if scores has one item fewer than hours?
It stops when the shorter list runs out, so the last hour gets no partner and is dropped, without any error.
A dictionary is a collection of pairs, each a key and its value. You give it the key and it gives you the value, the way a real dictionary gives you the meaning of a word. The course draws it as a two-column table: keys on the left, values on the right.
A dictionary as a two-column table. score_of['Chen'] finds the row whose key is 'Chen' and gives its value, 80. There is no row for 'Femi', so that lookup fails.
Compare it with the list of lesson P01.6.1. A list finds a value by its position, a whole number from 0 that you have to know. A dictionary finds it by a key you choose, here the student’s name. The keys play the part of the positions, so each key can appear only once, just as a list has only one position 2.
A set is a dictionary with the values taken away: a collection of keys and nothing else. It holds each item at most once and answers one question quickly: is this item in me?
Step 1. Writing a dictionary. Braces hold key: value pairs, separated by commas. Square brackets look a key up.
80 5
Step 2. Adding and overwriting. Assigning to a key that is not there yet adds a pair. Assigning to a key that is there replaces its value. Adding pair by pair builds the dictionary above from the two lists of the study table, with zip from the last lesson; then one value is overwritten:
True ['Ama', 'Ben', 'Chen', 'Dina', 'Eli']
45 5
list(built) gives the keys in the order they were added: a dictionary remembers its insertion order (guaranteed since Python 3.7). Overwriting Dina’s score kept five keys and left her in her place.
Step 3. A key that is not there. Looking up a missing key with square brackets stops the program:
KeyError: 'Femi'
That is the last line of the traceback, copied from Python 3.15 (wording varies a little between versions). Two tools avoid it. in asks whether a key is there: 'Chen' in score_of is True and 'Femi' in score_of is False. The method get returns the value if the key is there and a default otherwise: score_of.get('Femi') is None, and score_of.get('Femi', 0) is 0.
Step 4. Looping. A for loop over a dictionary visits its keys. To get each key with its value, loop over items(), which hands over (key, value) pairs ready to unpack:
Step 5. Which things can be keys. A key must never change, or the dictionary could not find it again, so keys are strings, numbers or tuples of those. A list cannot be a key: {['Ama', 2]: 55} stops with a TypeError that calls the list unhashable. The word comes from the way a dictionary finds a key: it turns the key into a number called its hash, which must stay the same for as long as the key is in the dictionary. Values, on the other hand, can be anything.
Step 6. A set. A set is written with braces but no colons, or made from a list with set:
3 ['distinction', 'fail', 'pass'] True
The five bands hold only three different words, and the set keeps each once. A set keeps no order, so sorted is used to print it the same way on every run. Two sets combine like the Venn diagrams of M01.6, if you have met them: | gives the union (in either), & the intersection (in both) and - the difference (in the first but not the second).
The words of two sentences, “the cat sat on the mat” and “the dog sat on the log”, as two sets. Each operation reads off regions of the picture. (Sets are drawn in alphabetical order; Python may print them in any order.)
A dictionary is a changeable collection of key–value pairs in which each key appears at most once. Keys must be hashable: an object whose hash never changes, such as a string, a number or a tuple of those. A dictionary remembers the order in which keys were added.
| Code | Meaning |
|---|---|
d[k] |
the value of key k; KeyError if k is missing |
d[k] = v |
add the pair, or replace the value of k |
k in d |
True if k is a key (values are not searched) |
d.get(k, x) |
the value of k, or x if k is missing (None if x is left out) |
d.items() |
the (key, value) pairs, in insertion order |
A set is a changeable collection of distinct hashable objects, with no order and no positions. set() is the empty set; {} is an empty dictionary. For sets A and B, A | B, A & B and A - B are the union, intersection and difference.
How big is a union? Split the picture into its three regions and let \(a\), \(c\) and \(b\) be the number of items only in \(A\), in both, and only in \(B\). Then \({\abs{A} = a + c}\) and \({\abs{B} = b + c}\), where \({\abs{A}}\) is the size of \(A\), len(A) in code. Adding the two sizes counts the middle region twice:
\[ \begin{aligned} \abs{A} + \abs{B} &= (a + c) + (b + c) \\ &= (a + b + c) + c \\ &= \abs{A \cup B} + \abs{A \cap B}. \end{aligned} \]
Taking \({\abs{A \cap B}}\) from both sides gives the rule
\[ \abs{A \cup B} = \abs{A} + \abs{B} - \abs{A \cap B}. \]
For the two sentences: \({5 + 5 - 3 = 7}\) different words, which is what len(first | second) gives.
A set or a dictionary does not search its items one by one. It turns the item into its hash, a whole number, and uses that number to work out where in its storage the item must be, then looks only there. Python’s documentation therefore lists x in s for a set, and k in d for a dictionary, as taking on average about the same time however many items there are, while x in a for a list takes time in proportion to its length. The price is the rule about keys: if a key could change, its hash would change, and the dictionary would look for it in the wrong place.
Counting the bands from scratch. The loop keeps a dictionary counts whose keys are the bands seen so far and whose values are how often each has appeared.
The counting pattern, one frame per pass. A band seen for the first time becomes a new key with count 1; a band seen before has its count raised by 1. The yellow row is the one that pass changed.
Pass 1. band is 'pass'. The dictionary is empty, so the test band in counts is False and the else branch adds the key: {'pass': 1}.
Pass 2. 'pass' again. Now the test is True, and the line counts[band] = counts[band] + 1 reads 1, adds 1 and stores 2 under the same key.
Passes 3 and 4. 'distinction' and then 'fail' are new, so each is added with count 1. After pass 4 the dictionary has three keys.
Pass 5. 'pass' is there, so its count goes from 2 to 3.
The result is {'pass': 3, 'distinction': 1, 'fail': 1}: three passes, one distinction, one fail. Two checks by hand: there are 3 keys, one per different band, and the counts add up to 5, one per score. The keys appear in the order in which each band was first met.
The worked example, with the bands computed from the scores by the cut-offs of P01.3.1:
def band_of(score):
if score >= 70:
return 'distinction'
elif score >= 50:
return 'pass'
else:
return 'fail'
bands = []
for score in scores:
bands.append(band_of(score))
counts = {}
for band in bands:
if band in counts:
counts[band] = counts[band] + 1
else:
counts[band] = 1
print(bands)
print(counts)['pass', 'pass', 'distinction', 'fail', 'pass']
{'pass': 3, 'distinction': 1, 'fail': 1}
The standard library has this pattern built in. Counter, from the module collections, is a dictionary made for counting: give it a list and it counts the items, and a missing key counts as 0 instead of raising KeyError.
Counter({'pass': 3, 'distinction': 1, 'fail': 1})
True 0 [('pass', 3)]
A vocabulary is a dictionary of the same kind: it gives every different word a number, in the order the words are first met. Text models start this way, because a model computes with numbers, not words.
{'the': 0, 'cat': 1, 'sat': 2, 'on': 3, 'mat': 4}
[0, 1, 2, 3, 0, 4]
The sentence of six words becomes the six numbers [0, 1, 2, 3, 0, 4]; the two copies of 'the' get the same number. Finally the sets of the figure in Build it up:
This module’s interactive, the box-diagram workbench, lives in lesson P01.6.4. It draws a list, but everything it shows about two names that share one object holds for a dictionary or a set just as well.
Wrong beside right. Left: += 1 must first read the old count, and a new band has none. Right: get with a default of 0 supplies the missing count.
“counts[band] += 1 counts the bands.”
Only once each band has a count. counts[band] += 1 means counts[band] = counts[band] + 1, which reads the old value first, and on the first pass there is none: KeyError: 'pass'. Test with in, as in the worked example, or write counts[band] = counts.get(band, 0) + 1, or use Counter.
“80 in score_of asks whether someone scored 80.”
in looks only at the keys, so 80 in score_of is False even though Chen scored 80. Ask the values directly: 80 in score_of.values() is True.
“{} makes an empty set.”
It makes an empty dictionary. The empty set is written set().
“A set keeps the items in the order I gave them.”
A set has no order and no positions, so kinds[0] is a TypeError and printing a set of words can show a different order from one run to the next. When order matters, print sorted(kinds), or use a list or a dictionary.
1. What does score_of['Chen'] give, and what does score_of['Femi'] do?
It gives 80. score_of['Femi'] stops the program with KeyError: 'Femi', because there is no such key.
2. What does score_of.get('Femi', 0) give?
0, the default, because 'Femi' is not a key. Without the default, get gives None.
3. Why is 80 in score_of False?
in on a dictionary checks the keys, and 80 is a value. 80 in score_of.values() is True.
4. How many items does set([55, 60, 55, 60]) hold?
Two, 55 and 60. A set keeps each item once.
5. For first = {'the', 'cat', 'sat', 'on', 'mat'} and second = {'the', 'dog', 'sat', 'on', 'log'}, what is first & second?
The words in both: 'on', 'sat' and 'the'.
1. ★ Build hours_of, a dictionary from each name to that student’s hours, with zip. What are hours_of['Chen'] and hours_of.get('Femi', 0), and how long is the dictionary after hours_of['Femi'] = 3?
2. ★★ The errors of predict on the study table are [4, -4, 2, -2, 0]. Count how many errors there are of each size, ignoring the sign, with a dictionary and get. Then check your answer with Counter.
The sizes are the absolute values, 4, 4, 2, 2 and 0, and the keys can be numbers:
sizes is {4: 2, 2: 2, 0: 1}: two errors of size 4, two of size 2 and one of 0. Building Counter from the list of sizes, [4, 4, 2, 2, 0], gives the same counts, and the counts add up to 5, one per student.
3. ★★★ Using only |, & and -, find the words that are in exactly one of the two sentences first and second. Predict how many there are before you compute them, and show that (first - second) | (second - first) gives the same set.
The words in exactly one sentence are those in the union but not in the intersection: (first | second) - (first & second). By the rule for the size of a union there are 7 words in the union and 3 in the intersection, so \({7 - 3 = 4}\). They are 'cat', 'dog', 'log' and 'mat'. The other way round, first - second is {'cat', 'mat'} and second - first is {'dog', 'log'}, and their union is the same four words: the two outer regions of the Venn diagram.
A dictionary finds a value by its key; a set keeps keys alone.
Look up by key, not by position.
d[k] looks up (a KeyError if missing), d[k] = v adds or replaces, k in d and d.get(k, x) are the safe tests.counts[x] = counts.get(x, 0) + 1, or Counter(items).in; |, & and - give union, intersection and difference.Sources. Each was opened and checked against this lesson on 12 November 2026.
KeyError while get returns None or a default, list(d) gives the keys in insertion order and in tests a key. Section 5.4, Sets: no duplicates, membership testing, and set(), not {}, for an empty set. Section 5.6: items().get never raises KeyError; Set Types: a set is an unordered collection of distinct hashable objects, with |, & and -.collections: Counter objects: a dict subclass for counting hashable objects, a count of zero for missing items, and most_common.x in s takes constant time on average for a set and a dictionary, and time in proportion to the length for a list. The Design and History FAQ explains why: a key’s hash, which depends on a per-process seed, gives the place to look.KeyError), 10.3 (in checks keys, not values), 10.4 (a collection of counters), 10.5 (a loop visits the keys) and 10.6 (a list cannot be a key, because keys must be hashable). Free under CC BY-NC-SA 4.0.vocabulary_.Next. P01.6.4 Sharing, copying and nesting shows what the arrows from names to objects mean when two names share one list, and copies the study table safely.
This lesson unlocks
P01.6.4 Sharing, copying and nesting