Dictionaries are Python's workhorse data structure. Here are techniques to use them effectively.
Safe Access with get()
user = {"name": "Owen", "age": 25}
# Risky - raises KeyError
email = user["email"] # KeyError!
# Safe - returns None
email = user.get("email") # None
# Safe - with default
email = user.get("email", "unknown@example.com")setdefault()
Get value or set and return default:
# Without setdefault
if "tags" not in user:
user["tags"] = []
user["tags"].append("python")
# With setdefault
user.setdefault("tags", []).append("python")One line instead of three.
defaultdict
Auto-create missing values:
from collections import defaultdict
# Regular dict
counts = {}
for word in words:
if word not in counts:
counts[word] = 0
counts[word] += 1
# defaultdict
counts = defaultdict(int)
for word in words:
counts[word] += 1 # Auto-creates 0
# Other defaults
lists = defaultdict(list)
sets = defaultdict(set)
nested = defaultdict(dict)Dictionary Comprehensions
# Basic
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# With condition
evens = {x: x**2 for x in range(10) if x % 2 == 0}
# Transform keys and values
upper = {k.upper(): v for k, v in data.items()}
# Swap keys and values
inverted = {v: k for k, v in original.items()}Merging Dictionaries
a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}
# Python 3.9+
merged = a | b # {"x": 1, "y": 3, "z": 4}
# Update in place
a |= b # a is now {"x": 1, "y": 3, "z": 4}
# Older Python
merged = {**a, **b}Later values override earlier ones.
Iteration Patterns
d = {"a": 1, "b": 2, "c": 3}
# Keys
for key in d:
print(key)
# Values
for value in d.values():
print(value)
# Both
for key, value in d.items():
print(f"{key}: {value}")Counter
Special dict for counting:
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "apple"]
counts = Counter(words)
# Counter({'apple': 3, 'banana': 1, 'cherry': 1})
counts.most_common(2) # [('apple', 3), ('banana', 1)]
counts["apple"] # 3
counts["missing"] # 0 (not KeyError)OrderedDict
Preserves insertion order (dict does too since 3.7, but OrderedDict has extra methods):
from collections import OrderedDict
od = OrderedDict()
od["first"] = 1
od["second"] = 2
od.move_to_end("first") # Move to end
od.popitem(last=False) # Pop first itemChainMap
Search multiple dicts:
from collections import ChainMap
defaults = {"color": "red", "size": "medium"}
user_prefs = {"color": "blue"}
settings = ChainMap(user_prefs, defaults)
settings["color"] # "blue" (from user_prefs)
settings["size"] # "medium" (from defaults)Common Patterns
Group by key
from collections import defaultdict
users = [
{"name": "Alice", "dept": "eng"},
{"name": "Bob", "dept": "sales"},
{"name": "Carol", "dept": "eng"},
]
by_dept = defaultdict(list)
for user in users:
by_dept[user["dept"]].append(user)Invert mapping
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}Filter dict
data = {"a": 1, "b": 2, "c": 3, "d": 4}
filtered = {k: v for k, v in data.items() if v > 2}
# {"c": 3, "d": 4}Nested access
def get_nested(d, *keys, default=None):
for key in keys:
if isinstance(d, dict):
d = d.get(key)
else:
return default
return d if d is not None else default
data = {"user": {"profile": {"name": "Owen"}}}
get_nested(data, "user", "profile", "name") # "Owen"
get_nested(data, "user", "missing", "key") # NoneMy Rules
- Use get() — avoid KeyError
- Use defaultdict — for grouping/counting
- Use Counter — for counting specifically
- Use comprehensions — for transformations
- Use | — for merging (Python 3.9+)
Master dictionaries and you master Python data handling.
React to this post: