Dictionaries and Nested Records
What You'll Learn
How to create, access, and manipulate dictionaries — Python's most important data structure for real-world data.
What Is a Dictionary?
A dictionary stores key-value pairs. Keys must be unique and immutable (strings, numbers, tuples). Values can be anything.
user = {
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"active": True,
}
Think of it as a lookup table: given a key, get a value instantly (O(1)).
Creating Dictionaries
# Literal
person = {"name": "Bob", "age": 25}
# Empty
config = {}
# From key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs)
# From keyword arguments
d = dict(name="Alice", age=30)
# Dict comprehension
squares = {n: n**2 for n in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Accessing Values
user = {"name": "Alice", "age": 30}
# Direct access — raises KeyError if key missing
print(user["name"]) # Alice
# .get() — returns None (or default) if key missing
print(user.get("email")) # None
print(user.get("email", "N/A")) # N/A
# Check if key exists
print("name" in user) # True
print("phone" in user) # False
Modifying Dictionaries
user = {"name": "Alice", "age": 30}
# Add or update
user["email"] = "alice@example.com"
user["age"] = 31
# Update multiple at once
user.update({"city": "London", "age": 32})
# Remove
del user["age"] # raises KeyError if missing
email = user.pop("email") # remove and return value
user.pop("phone", None) # no error if key missing
user.clear() # remove all
Iterating Over Dictionaries
scores = {"Alice": 95, "Bob": 82, "Charlie": 74}
# Keys only
for name in scores:
print(name)
# Values only
for score in scores.values():
print(score)
# Key and value together
for name, score in scores.items():
print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 82
# Charlie: 74
Useful Dictionary Methods
d = {"a": 1, "b": 2, "c": 3}
d.keys() # dict_keys(['a', 'b', 'c'])
d.values() # dict_values([1, 2, 3])
d.items() # dict_items([('a', 1), ('b', 2), ('c', 3)])
# setdefault — set a key only if it doesn't exist
d.setdefault("d", 4) # adds d=4
d.setdefault("a", 99) # a stays 1
# Merge two dicts (Python 3.9+)
defaults = {"timeout": 30, "retries": 3}
overrides = {"timeout": 60}
config = defaults | overrides
# {"timeout": 60, "retries": 3}
Nested Dictionaries
Real-world data often has nested structures:
users = {
"alice": {
"email": "alice@example.com",
"scores": [95, 88, 91],
"address": {
"city": "London",
"country": "UK",
}
},
"bob": {
"email": "bob@example.com",
"scores": [72, 85, 78],
"address": {
"city": "Paris",
"country": "FR",
}
}
}
# Access nested values
print(users["alice"]["address"]["city"]) # London
print(users["bob"]["scores"][0]) # 72
# Safe access with .get()
city = users.get("charlie", {}).get("address", {}).get("city", "Unknown")
print(city) # Unknown
Dict Comprehensions
# Invert a dictionary
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Filter a dictionary
scores = {"Alice": 95, "Bob": 62, "Charlie": 74}
passing = {name: score for name, score in scores.items() if score >= 70}
# {'Alice': 95, 'Charlie': 74}
# Transform values
doubled = {k: v * 2 for k, v in original.items()}
# {'a': 2, 'b': 4, 'c': 6}
defaultdict — Never Hit a Missing Key
from collections import defaultdict
# Automatically creates missing keys with a default value
word_count = defaultdict(int) # default = 0
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
word_count[word] += 1 # no KeyError even on first access
print(dict(word_count))
# {'apple': 3, 'banana': 2, 'cherry': 1}
Another common pattern — grouping:
from collections import defaultdict
people = [
("Alice", "Engineering"),
("Bob", "Marketing"),
("Charlie", "Engineering"),
("Diana", "Marketing"),
]
by_dept = defaultdict(list)
for name, dept in people:
by_dept[dept].append(name)
# {'Engineering': ['Alice', 'Charlie'], 'Marketing': ['Bob', 'Diana']}
Counter — Count Things Easily
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)
print(counts)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(counts.most_common(2))
# [('apple', 3), ('banana', 2)]
# Counters support arithmetic
a = Counter({"x": 3, "y": 1})
b = Counter({"x": 1, "z": 2})
print(a + b) # Counter({'x': 4, 'z': 2, 'y': 1})
Common Patterns
# Count occurrences (manual)
counts = {}
for item in items:
counts[item] = counts.get(item, 0) + 1
# Group by a key
groups = {}
for item in data:
key = item["department"]
groups.setdefault(key, []).append(item)
# Frequency table
from collections import Counter
freq = Counter(items)
Common Mistakes
| Mistake | Fix |
|---|---|
d["missing_key"] | Use d.get("key", default) |
| Iterating while modifying | Iterate over list(d.keys()) |
| Mutating a shared dict accidentally | Use d.copy() or dict(d) for a shallow copy |
{} for empty set | Use set() — {} makes an empty dict |
Quick Reference
# Create
d = {}
d = {"key": "value"}
d = dict(key="value")
d = {k: v for k, v in pairs}
# Access
d["key"]
d.get("key", default)
"key" in d
# Modify
d["key"] = value
d.update(other_dict)
del d["key"]
d.pop("key", None)
# Iterate
for k in d:
for v in d.values():
for k, v in d.items():
# Merge (3.9+)
merged = d1 | d2
# Useful collections
from collections import defaultdict, Counter