Skip to main content

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​

Quiz

Hashable keys

1 / 2

Which 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​

Quiz

Dict comprehensions

1 / 2

What 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​

Quiz

Merging mappings

1 / 2

After 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​

Quiz

Mapping patterns

1 / 2

A 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​

Quiz

Missing keys

1 / 2

defaultdict(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​

TypeRole
OrderedDictEquality includes order; move_to_end; better when you reorder often (LRU-style). Plain dict already keeps insertion order.
ChainMapSearch several mappings as one. Writes go to the first map.
CounterInteger counts; + / -; most_common(n).
types.MappingProxyTypeRead-only view of a live dict (updates to the original show through).
shelveDisk-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​

Quiz

Variants and views

1 / 2

ChainMap(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​

Quiz

Sets

1 / 2

len(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​

Key takeaways
  1. Dicts are everywhere in the language. Keys must be hashable; order is insertion order.
  2. Dictcomps, **, and | / |= are the modern ways to build and merge mappings.
  3. Mapping match is a partial match on keys; it will not auto-insert missing keys.
  4. Prefer setdefault or defaultdict when you grow lists (or other mutables) per key. __missing__ only hooks d[k].
  5. Views are live; keys() (and items() when values are hashable) speak set operators.
  6. Sets enforce uniqueness and express algebra (| & - ^). Empty set is set().

Exercises​

Activity: Set comprehension

Read words. Print a sorted unique list of first letters, built with a set comprehension, space-separated.