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.
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.
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.
# 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.
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".
{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