Python defaultdict auto-grouping

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 in branch.
  • 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

  1. It never raises KeyError. A typo like groups['categroy'] silently creates a new empty list. If you must tell a missing key from an empty one, use a plain dict and check explicitly.
  2. Serialization. dict(dd) only converts the top level. For nested defaultdict objects, convert recursively or pass default=str to json.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.