How do I avoid KeyError when grouping items into a dictionary in Python?
Short answer
Use collections.defaultdict with a factory like list or int so a missing key is created automatically on first access. This removes the check-then-create boilerplate that a plain dict needs.
The short version
Use collections.defaultdict, passing a factory such as list or int. When you access a key that does not exist yet, defaultdict creates it automatically using the factory, so you can append or add straight away without a KeyError. This removes the repetitive "check if the key exists, if not create it" code a plain dict forces on you.
Grouping values by key
from collections import defaultdict
groups = defaultdict(list)
for city, value in [("Leeds", 1), ("Leeds", 2), ("Bath", 3)]:
groups[city].append(value)
print(dict(groups)) # {'Leeds': [1, 2], 'Bath': [3]}
The first time groups["Leeds"] is touched, defaultdict creates an empty list for it, so append just works. There is no need to check whether the key is already there.
The boilerplate it replaces
With a plain dict you have to create the list yourself on first sight of a key.
groups = {}
for city, value in pairs:
if city not in groups:
groups[city] = []
groups[city].append(value)
This works but repeats the check-and-create on every loop. defaultdict(list) folds that away.
Counting with a default of int
Passing int as the factory gives a default of 0, which makes counting clean.
counts = defaultdict(int)
for word in ["a", "b", "a"]:
counts[word] += 1
print(dict(counts)) # {'a': 2, 'b': 1}
The first counts[word] for a new word starts at 0, so += 1 needs no special case.
The subtle side effect
Merely accessing a missing key creates it, which can surprise you. Reading groups["York"] to check it adds an empty York entry as a side effect.
print(groups["York"]) # [] , but this also inserts 'York'!
If you only want to look without creating, use .get("York") on the defaultdict, or test with in, which do not trigger the factory. This accidental insertion is the one gotcha to remember.
The plain-dict alternatives
For counting specifically, collections.Counter is even more direct. And a plain dict's setdefault("key", []).append(value) achieves the same grouping without importing anything, though it reads less cleanly. Reach for defaultdict when you are building up grouped or counted data in a loop and want the missing-key handling to disappear.