Dictionaries and Sets
Source: Fluent Python, 2nd Edition — Chapter 3 by Luciano Ramalho
Python programs are full of dicts, even when you never write {...}. Class attributes, instance __dict__, module namespaces, and keyword arguments are mappings. Built-ins live in __builtins__.__dict__. That is why dict is heavily optimized, and why hash tables show up again for set and frozenset.
Sets give you the usual math: union, intersection, difference, subset tests. Used well, they replace nested loops and if chains.
This chapter is about using mappings and sets. We will cover:
- Dict comprehensions,
**unpacking, and|/|=merges (3.9+) - Mapping patterns in
match(3.10+) get,setdefault,defaultdict, and__missing__- Nearby types:
OrderedDict,ChainMap,Counter, views, read-only proxies - Sets, set comprehensions, and set ops on
dict.keys()/dict.items()
Why dicts matter
Keys must be hashable: a stable __hash__, and __eq__ that agrees with the hash. str, numbers, and frozenset are hashable. A tuple is hashable only if every item is. A list is never a valid dict key.
Lookups by key are fast even for huge dicts. Since Python 3.7, insertion order of keys is part of the language. Dicts still cost extra memory compared to a plain array of pointers. Adding instance attributes after __init__ can make an object’s __dict__ less compact.
d.get(k, default) is for “missing is OK.” d[k] is fail-fast (KeyError).
🧪 Hashable vs not
Mini quiz — keys and hashability
Hashable keys
1 / 2Which can be a dict key?
Pick an answer to see if you are right.
Dict comprehensions
A dictcomp builds a mapping from key: value pairs:
pairs = [(1, "a"), (2, "b")]
swapped = {word: n for n, word in pairs}
# {'a': 1, 'b': 2}
You can filter and transform in the same expression, same idea as listcomps.
🧪 Invert and filter
Mini quiz — dictcomps
Dict comprehensions
1 / 2What does this produce?
{c: i for i, c in enumerate("ab")}
Pick an answer to see if you are right.
Challenge: invert pairs
Each input token is key=value (both words). Build a dict with values as keys and original keys as values, using a dict comprehension. Print newkey:oldkey pairs sorted by the new key.
Unpacking and merging
** splats a mapping into a dict literal or into keyword arguments. Later keys win in a literal:
{"a": 0, **{"x": 1}, "y": 2, **{"x": 4}}
# {'a': 0, 'x': 4, 'y': 2}
Python 3.9+ also has union on mappings, same symbols as sets:
d1 | d2 # new dict; keys from d2 overwrite d1
d1 |= d2 # update d1 in place
The left operand’s type is usually the result type for |.
🧪 ** and |
Mini quiz — merge
Merging mappings
1 / 2After d1 | d2, if both have key "a", the value comes from
Pick an answer to see if you are right.
Challenge: prefer the right
Two dicts as k=v groups split by |. Merge with | (right wins). Print key=value for keys sorted.
Mapping patterns
match can destructure mappings. Extra keys on the subject are OK (partial match). Key order in the pattern does not matter.
match record:
case {"kind": "book", "who": name}:
...
case {"kind": "film", "who": name}:
...
case {"kind": "book"}:
... # incomplete book
case _:
...
Capture leftovers with **details as the last entry in the pattern. Matching uses get-style lookup: it does not create missing keys (unlike some defaultdict tricks).
🧪 match a record
Mini quiz — mapping match
Mapping patterns
1 / 2A mapping pattern matches if
Pick an answer to see if you are right.
Challenge: label a record
Tokens: kind then who then optional extra ignored. Print the who if kind is book or film, else ?. Use match on a dict.
Missing keys: get, setdefault, defaultdict
Updating a mutable value in a dict is a common trap:
# two lookups (or three if missing)
xs = d.get(k, [])
xs.append(v)
d[k] = xs
setdefault does “get or insert” in one hash lookup:
d.setdefault(k, []).append(v)
collections.defaultdict(list) makes d[k].append(v) work even when k is new. The factory runs only for d[k] (__getitem__), not for .get or in.
If you subclass dict, __missing__ is called from __getitem__ only. Prefer collections.UserDict when you grow a custom mapping: it stores items in .data and avoids dict shortcuts that force extra overrides.
🧪 setdefault vs defaultdict
Mini quiz — missing keys
Missing keys
1 / 2defaultdict(list): k in dd when k was never assigned
Pick an answer to see if you are right.
Challenge: group values
Tokens name score repeating. Group scores per name with setdefault. Print names sorted, each as name:s1,s2.
Other mapping types
| Type | Role |
|---|---|
OrderedDict | Equality includes order; move_to_end; better when you reorder often (LRU-style). Plain dict already keeps insertion order. |
ChainMap | Search several mappings as one. Writes go to the first map. |
Counter | Integer counts; + / -; most_common(n). |
types.MappingProxyType | Read-only view of a live dict (updates to the original show through). |
shelve | Disk-backed mapping of string keys to picklable values (know the pickle caveats). |
d.keys(), .values(), .items() return views: cheap, live, not a copied list. values is not a set. keys always behaves like a set of hashable keys. items is a set only if values are hashable too.
d1.keys() & d2.keys() # set of shared keys
🧪 Counter, ChainMap, views
Mini quiz — variants and views
Variants and views
1 / 2ChainMap(a, b)["k"] if k is in both
Pick an answer to see if you are right.
Challenge: shared keys
Two k=v groups split by |. Print the shared keys sorted, space-separated, using key views and &. If none, print -.
Sets
A set holds unique hashable objects. frozenset is the immutable (and hashable) sibling, so it can live inside another set or be a dict key.
{"spam", "eggs"} | {"eggs", "bacon"} # union
needles & haystack # intersection
Empty set is set(), never {}. {1, 2, 3} is faster than set([1, 2, 3]).
Setcomps: {x for x in items if ...}
Operators like & want set operands. Methods such as .union(it) accept any iterable of hashable items. Star-unpack: {*a, *b, *c}.
Hash tables mean: membership is fast; sets use extra memory; do not rely on element order; growing a set can reshuffle existing items.
To drop duplicates but keep first seen order, list(dict.fromkeys(items)) is the usual trick (dict keys are unique and ordered).
🧪 Set ops and setcomp
Mini quiz — sets
Sets
1 / 2len(needles & haystack) counts
Pick an answer to see if you are right.
Challenge: overlap count
Two word lists split by |. Print how many words appear in both, using set &.
Chapter summary
- Dicts are everywhere in the language. Keys must be hashable; order is insertion order.
- Dictcomps,
**, and|/|=are the modern ways to build and merge mappings. - Mapping
matchis a partial match on keys; it will not auto-insert missing keys. - Prefer
setdefaultordefaultdictwhen you grow lists (or other mutables) per key.__missing__only hooksd[k]. - Views are live;
keys()(anditems()when values are hashable) speak set operators. - Sets enforce uniqueness and express algebra (
| & - ^). Empty set isset().
Exercises
Read words. Print a sorted unique list of first letters, built with a set comprehension, space-separated.