Dictionaries

Use the structure Python itself is built out of: lookups with a default, grouping, counting and merging.

06of 24
≈ 13Min read
5Parts

Keys to values, in insertion order

Since Python 3.7 a dict keeps insertion order — that is a language guarantee, not an implementation detail. Keys must be hashable, exactly as set members must.

Objects, classes and modules are dicts underneath: obj.__dict__, globals(), locals(). Learning this structure well is learning a lot of the language.

py
person = {"name": "Ada", "age": 36}

person["name"]              # 'Ada'
person["email"]             # KeyError — the loud one
person.get("email")         # None — the quiet one
person.get("email", "—")    # a default of your choosing

person["email"] = "a@b.c"   # adds or replaces
del person["age"]
person.pop("age", None)     # remove and return, without raising

"name" in person            # tests the KEYS, not the values
len(person)

Iterating

Changing the size of a dict while iterating it raises RuntimeError. Iterate over list(d.items()) if you intend to add or delete inside the loop.

py
for key in person:                   # keys, by default
    ...

for key, value in person.items():    # the one you almost always want
    print(f"{key}: {value}")

list(person.keys())
list(person.values())

# the views are live: they reflect later changes, and they do not copy

The two defaults

defaultdict(list) is the standard way to group; defaultdict(int) is the standard way to count by hand. The one catch is that merely reading a missing key creates it, which can hide a typo — call dict(groups) when you are done if that matters.

py
# setdefault: get it, or put it there and get that
groups = {}
for word in words:
    groups.setdefault(word[0], []).append(word)

# defaultdict: the same idea, decided once
from collections import defaultdict
groups = defaultdict(list)
for word in words:
    groups[word[0]].append(word)      # the empty list appears on demand

Counting, without writing the loop

most_common breaks ties by first appearance, because Counter is a dict and dicts keep order.

py
from collections import Counter

counts = Counter("mississippi")
counts["s"]                  # 4
counts["z"]                  # 0 — a missing key is zero, not an error
counts.most_common(2)        # [('i', 4), ('s', 4)]
sum(counts.values())         # 11

Counter(words) - Counter(stopwords)      # counters subtract

Building and merging

| merges into a new dict and the right-hand side wins on a clash — which is exactly the shape of "defaults, then what the user configured".

py
{k: v for k, v in pairs if v}         # a dict comprehension
{v: k for k, v in person.items()}     # inverted — beware duplicate values
dict(zip(names, ages))                # two lists into one dict
dict.fromkeys(names, 0)               # every name mapped to 0

defaults | overrides                  # merged, right wins (3.9+)
{**defaults, **overrides}             # the same, older spelling
settings |= overrides                 # in place

Practice