defaultdict for Implicit Init
`collections.defaultdict` removes the boilerplate of checking-then-initialising before every increment or append. It supplies a default value when a missing key is read, and that default lives in the dict from then on. This snippet covers the bucket-by-key pattern with `defaultdict(list)`, the count pattern with `defaultdict(int)`, and a nested `defaultdict` for two-level groupings.
348 views
2
from collections import defaultdict
rows = [
{'role': 'admin', 'name': 'Ada'},
{'role': 'member', 'name': 'Bo'},
{'role': 'admin', 'name': 'Cal'},
{'role': 'member', 'name': 'Di'},
]
by_role = defaultdict(list)
for row in rows:
by_role[row['role']].append(row['name'])
print(dict(by_role))
# {'admin': ['Ada', 'Cal'], 'member': ['Bo', 'Di']}Without defaultdict, the loop body would be if role not in by_role: by_role[role] = [], then by_role[role].append(...). defaultdict(list) collapses that into one line: any read of a missing key auto-creates an empty list and inserts it. The defaultdict constructor takes a factory (here list), called with no arguments to produce the default. Wrap the result in dict() for printing if you want a clean output, since the repr of a defaultdict shows the factory.
from collections import defaultdict
logs = ['/api/users', '/api/auth', '/api/users', '/api/auth', '/api/auth', '/health']
counts = defaultdict(int)
for path in logs:
counts[path] += 1
print(dict(counts))
# {'/api/users': 2, '/api/auth': 3, '/health': 1}
# Equivalent with Counter:
from collections import Counter
print(dict(Counter(logs)))
# {'/api/users': 2, '/api/auth': 3, '/health': 1}defaultdict(int) initialises missing keys to 0, which makes counts[key] += 1 a one-liner instead of a if key in counts else 0 dance. For pure counting, Counter is even more idiomatic and supports most_common, but defaultdict(int) is the right answer when the count is mixed with other per-key state (sums, running averages). The choice between the two usually comes down to whether you want extra Counter-specific methods or a more general 'every-key-has-a-default' shape.
from collections import defaultdict
events = [
{'user': 'ada', 'action': 'login', 'count': 3},
{'user': 'bo', 'action': 'login', 'count': 1},
{'user': 'ada', 'action': 'view', 'count': 12},
{'user': 'bo', 'action': 'view', 'count': 5},
{'user': 'ada', 'action': 'login', 'count': 2},
]
by_user_action = defaultdict(lambda: defaultdict(int))
for e in events:
by_user_action[e['user']][e['action']] += e['count']
print({u: dict(actions) for u, actions in by_user_action.items()})
# {'ada': {'login': 5, 'view': 12}, 'bo': {'login': 1, 'view': 5}}When the natural shape is a nested mapping (user -> action -> count), wrapping the inner dict in another defaultdict keeps the nested loop body just as terse as the flat case. The lambda: defaultdict(int) is the factory: each new outer key gets a fresh inner defaultdict(int). Watch out for serialisation: a nested defaultdict still answers __getitem__ on missing keys when you read it later, which can mutate it during a JSON dump. Convert to a plain dict with the comprehension above before serialising to be safe.
