
The smell everyone writes
You have probably written this:
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
Or the worse version that groups items by category:
groups = {}
for item in items:
if item.category not in groups:
groups[item.category] = []
groups[item.category].append(item)
Both work, but the if key not in guard and the get(key, []) default are pure boilerplate and easy to get wrong.
The 2-line fix
from collections import defaultdict
counts = defaultdict(int)
for word in words:
counts[word] += 1
groups = defaultdict(list)
for item in items:
groups[item.category].append(item)
No guards, no defaults. defaultdict calls the factory you give it (int, list, set, …) the first time a key is missing, stores the result, and returns it.
Why it is better
- Less code, fewer bugs. You cannot forget the
if not inbranch. - Reads top-to-bottom. The intent is to group by category, not to build a dict carefully.
- Any factory works:
defaultdict(set),defaultdict(lambda: [0, 0]), and so on.
Two gotchas
- It never raises
KeyError. A typo likegroups['categroy']silently creates a new empty list. If you must tell a missing key from an empty one, use a plaindictand check explicitly. - Serialization.
dict(dd)only converts the top level. For nesteddefaultdictobjects, convert recursively or passdefault=strtojson.dumps.
When not to use it
If you must detect a genuinely missing key, such as a config lookup with a fallback error, a normal dict with .get() or in is clearer. defaultdict is for accumulation, not lookups.