Dictionary Methods and Counting Patterns
setdefault, get with a default and the Counter class turn the most common dictionary jobs - counting, grouping and accumulating - into two or three lines.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
The full method list
| Method | Does | Returns |
|---|---|---|
get(k[, default]) | Look up without raising | value or default |
setdefault(k[, default]) | Look up, inserting the default if absent | the value now stored |
pop(k[, default]) | Remove by key | the value |
popitem() | Remove the last inserted pair | (key, value) |
update(other) | Add or overwrite from another mapping | None |
keys(), values(), items() | Live views | a view object |
copy() | Shallow copy | a new dict |
clear() | Remove everything | None |
dict.fromkeys(keys[, v]) | Build from a key list | a new dict |
setdefault
setdefault returns the value for a key, inserting a default first if the key is absent. It is the tool for building a dictionary of collections.
groups = {}
# Verbose
for word in ["apple", "avocado", "banana"]:
initial = word[0]
if initial not in groups:
groups[initial] = []
groups[initial].append(word)
# With setdefault
groups = {}
for word in ["apple", "avocado", "banana"]:
groups.setdefault(word[0], []).append(word)
print(groups) # {'a': ['apple', 'avocado'], 'b': ['banana']}d = {"a": 1}
print(d.setdefault("a", 99)) # 1 - already present, default ignored
print(d.setdefault("b", 99)) # 99 - inserted
print(d) # {'a': 1, 'b': 99}getnever changes the dictionary;setdefaultmay. That is the whole difference, and it is whysetdefaultis right for accumulating and wrong for a plain lookup.
Counting
text = "the cat sat on the mat the end"
# With get
counts = {}
for word in text.split():
counts[word] = counts.get(word, 0) + 1
print(counts)
# With Counter, from the standard library
from collections import Counter
counts = Counter(text.split())
print(counts) # Counter({'the': 3, 'cat': 1, ...})
print(counts["the"]) # 3
print(counts["missing"]) # 0 - never raises
print(counts.most_common(2)) # [('the', 3), ('cat', 1)]
print(sum(counts.values())) # 8 - total wordsfrom collections import Counter
print(Counter("mississippi")) # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
print(Counter([1, 1, 2, 3, 3, 3]).most_common(1)) # [(3, 3)]
a = Counter("aabbc")
b = Counter("abbbd")
print(a + b) # counts added
print(a - b) # counts subtracted, negatives dropped
print(a & b) # minimum of each
print(a | b) # maximum of eachdefaultdict
from collections import defaultdict
groups = defaultdict(list) # a missing key auto-creates an empty list
for word in ["apple", "avocado", "banana"]:
groups[word[0]].append(word)
print(dict(groups)) # {'a': ['apple', 'avocado'], 'b': ['banana']}
totals = defaultdict(int) # a missing key auto-creates 0
for item, amount in [("pen", 10), ("book", 50), ("pen", 5)]:
totals[item] += amount
print(dict(totals)) # {'pen': 15, 'book': 50}
index = defaultdict(set)
for name, tag in [("a", "x"), ("b", "x"), ("a", "y")]:
index[tag].add(name)
print(dict(index)) # {'x': {'a', 'b'}, 'y': {'a'}}The one catch: merely reading a missing key creates it.
d = defaultdict(list)
print(len(d)) # 0
d["never set"] # a read...
print(len(d)) # 1 ...that created a keyChoosing between the three
| Situation | Use |
|---|---|
| Counting occurrences | Counter |
| Grouping into lists or sets | defaultdict |
| Occasional accumulation in a plain dict | setdefault |
| Reading a value that may be absent | get |
Inverting a dictionary
ages = {"Meera": 27, "Arun": 31, "Sara": 27}
# Simple inversion: later duplicates overwrite earlier ones
by_age = {age: name for name, age in ages.items()}
print(by_age) # {27: 'Sara', 31: 'Arun'} - Meera was lost
# Keeping every name
from collections import defaultdict
by_age = defaultdict(list)
for name, age in ages.items():
by_age[age].append(name)
print(dict(by_age)) # {27: ['Meera', 'Sara'], 31: ['Arun']}Sorting a dictionary
scores = {"Meera": 92, "Arun": 78, "Sara": 85}
print(dict(sorted(scores.items()))) # by key
print(dict(sorted(scores.items(), key=lambda p: p[1]))) # by value
print(dict(sorted(scores.items(), key=lambda p: -p[1]))) # by value, descending
print(max(scores, key=scores.get)) # Meera
print(min(scores.values()), max(scores.values()))max(scores, key=scores.get) reads oddly at first: it iterates the keys, scoring each by its value, and returns the winning key.
Aggregating records
sales = [
{"region": "north", "amount": 1200},
{"region": "south", "amount": 800},
{"region": "north", "amount": 400},
{"region": "east", "amount": 950},
]
from collections import defaultdict
totals = defaultdict(int)
counts = defaultdict(int)
for record in sales:
totals[record["region"]] += record["amount"]
counts[record["region"]] += 1
for region in sorted(totals, key=totals.get, reverse=True):
average = totals[region] / counts[region]
print(f"{region:<8}{totals[region]:>8,}{average:>10,.0f}")Copying
import copy
original = {"a": [1, 2], "b": 3}
alias = original # not a copy
shallow = original.copy() # or dict(original)
deep = copy.deepcopy(original)
original["a"].append(3)
print(shallow["a"]) # [1, 2, 3] - the inner list is shared
print(deep["a"]) # [1, 2]Common mistakes
- Using
setdefaultfor a plain read, quietly growing the dictionary. - Reading a missing key from a
defaultdictand creating it by accident. - Inverting a dictionary with duplicate values and losing entries.
- Forgetting that
updatereturnsNone. - Using
Counterand expecting aKeyErrorfor a missing item; it returns 0. - Assuming
copy()protects nested values.
Best practices
- Reach for
Counterthe moment you writecounts[x] = counts.get(x, 0) + 1. - Reach for
defaultdictthe moment you writeif key not in d: d[key] = []. - Convert a
defaultdictback withdict()before returning it from a public function, so callers do not inherit the auto-creation. - Sort with
key=rather than building intermediate lists.
Practice
- Count word frequencies in a paragraph and print the five most common.
- Group a list of file names by extension using both
setdefaultanddefaultdict. - Invert a dictionary that has duplicate values without losing any keys.
- Find the key with the largest value without sorting the whole dictionary.
- Explain the one behaviour of
defaultdictthat can surprise you, with an example.
Conclusion
Counting, grouping and accumulating are the three jobs dictionaries do most. Counter handles the first, defaultdict the second, and setdefault covers the rest when you would rather not import anything.