05-04: Dictionaries¶
A dictionary (dict) is an unordered (Python 3.7+ preserves insertion order), mutable collection of key-value pairs. It provides fast lookup by key.
Creating Dictionaries¶
# Curly brace syntax
person = {"name": "Alice", "age": 30, "job": "Engineer"}
# Empty dict
empty = {}
empty2 = dict()
# dict() constructor with keyword arguments
d = dict(name="Bob", age=25, city="NYC")
# dict() from list of tuples
d2 = dict([("x", 10), ("y", 20), ("z", 30)])
# From zip
keys = ["a", "b", "c"]
vals = [1, 2, 3]
d3 = dict(zip(keys, vals))
print(d3) # {'a': 1, 'b': 2, 'c': 3}
# Dict comprehension
squares = {x: x**2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Keys and Values¶
- Keys must be hashable (immutable): strings, numbers, tuples — not lists or dicts
- Values can be anything — including lists, dicts, functions, etc.
- Keys must be unique — assigning to an existing key updates its value
# Various key types
mixed_keys = {
"string_key": "value1",
42: "value2",
(1, 2): "value3", # tuple key OK
True: "value4",
}
# List as key — NOT allowed
# {[1,2]: "value"} # TypeError: unhashable type: 'list'
Accessing Values¶
Direct access with []¶
person = {"name": "Alice", "age": 30}
print(person["name"]) # Alice
print(person["age"]) # 30
# KeyError if key doesn't exist
print(person["email"]) # KeyError!
get() — safe access with default¶
print(person.get("name")) # Alice
print(person.get("email")) # None (no error)
print(person.get("email", "N/A")) # N/A (custom default)
Adding and Updating¶
person = {"name": "Alice", "age": 30}
# Add new key
person["email"] = "alice@example.com"
print(person) # {..., 'email': 'alice@example.com'}
# Update existing key
person["age"] = 31
print(person["age"]) # 31
# update() — merge another dict
person.update({"city": "NYC", "age": 32})
print(person)
# update() with keyword arguments
person.update(zip=10001, country="USA")
# setdefault() — add key only if not present
person.setdefault("nickname", "Ally")
person.setdefault("name", "Bob") # does nothing, 'name' already exists
print(person["nickname"]) # Ally
print(person["name"]) # Alice (unchanged)
Removing Items¶
person = {"name": "Alice", "age": 30, "city": "NYC", "job": "Eng"}
# del — delete by key (KeyError if not found)
del person["city"]
print(person)
# pop() — remove and return value
age = person.pop("age")
print(age) # 30
print(person) # {'name': 'Alice', 'job': 'Eng'}
# pop with default (no error if key absent)
email = person.pop("email", "not found")
print(email) # not found
# popitem() — remove and return last inserted item as tuple
item = person.popitem()
print(item) # ('job', 'Eng')
# clear() — remove all items
person.clear()
print(person) # {}
Iterating¶
inventory = {"apple": 50, "banana": 30, "cherry": 80}
# Iterate over keys (default)
for key in inventory:
print(key)
# Iterate over keys explicitly
for key in inventory.keys():
print(key)
# Iterate over values
for value in inventory.values():
print(value)
# Iterate over key-value pairs
for key, value in inventory.items():
print(f"{key}: {value}")
# Sorted iteration
for key in sorted(inventory):
print(f"{key}: {inventory[key]}")
Checking Keys¶
d = {"a": 1, "b": 2, "c": 3}
print("a" in d) # True
print("z" in d) # False
print("a" not in d) # False
# Check values
print(1 in d.values()) # True
print(99 in d.values()) # False
# Check key-value pair
print(("a", 1) in d.items()) # True
Dictionary Methods¶
d = {"a": 1, "b": 2, "c": 3}
print(len(d)) # 3
print(list(d.keys())) # ['a', 'b', 'c']
print(list(d.values())) # [1, 2, 3]
print(list(d.items())) # [('a', 1), ('b', 2), ('c', 3)]
# Convert to dict
print(dict(d.items())) # {'a': 1, 'b': 2, 'c': 3}
Dictionary Comprehensions¶
# Basic
squares = {x: x**2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# With condition
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}
print(even_squares) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# Invert a dict
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
# From two lists
keys = ["name", "age", "job"]
vals = ["Alice", 30, "Engineer"]
profile = {k: v for k, v in zip(keys, vals)}
print(profile)
# Filtering
inventory = {"apple": 50, "banana": 0, "cherry": 30, "date": 0}
in_stock = {k: v for k, v in inventory.items() if v > 0}
print(in_stock) # {'apple': 50, 'cherry': 30}
Nested Dictionaries¶
students = {
"alice": {"grade": "A", "score": 95, "courses": ["Math", "Physics"]},
"bob": {"grade": "B", "score": 82, "courses": ["English", "History"]},
}
print(students["alice"]["grade"]) # A
print(students["bob"]["courses"][0]) # English
# Add a new student
students["charlie"] = {"grade": "A+", "score": 98, "courses": ["CS", "Math"]}
# Update nested
students["alice"]["score"] = 97
# Iterate nested
for name, info in students.items():
print(f"{name}: grade={info['grade']}, score={info['score']}")
Merging Dictionaries¶
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 3}
# Python 3.9+ — merge operator
merged = d1 | d2 # d2 wins on conflict
print(merged) # {'a': 1, 'b': 20, 'c': 3}
# Python 3.5+ — double star unpacking
merged2 = {**d1, **d2} # same result
print(merged2)
# update() — modifies d1 in place
d1.update(d2)
print(d1) # {'a': 1, 'b': 20, 'c': 3}
defaultdict — Default Values for Missing Keys¶
from collections import defaultdict
# Count word frequencies
word_count = defaultdict(int)
text = "the cat sat on the mat the cat"
for word in text.split():
word_count[word] += 1 # no KeyError!
print(dict(word_count))
# {'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1}
# Group items
from collections import defaultdict
groups = defaultdict(list)
data = [("Alice", "Math"), ("Bob", "Physics"), ("Alice", "CS"), ("Bob", "Math")]
for name, course in data:
groups[name].append(course)
print(dict(groups))
# {'Alice': ['Math', 'CS'], 'Bob': ['Physics', 'Math']}
Counter — Count Occurrences¶
from collections import Counter
# Count characters
c = Counter("banana")
print(c) # Counter({'a': 3, 'n': 2, 'b': 1})
print(c['a']) # 3
print(c['z']) # 0 (no KeyError)
# Most common
print(c.most_common(2)) # [('a', 3), ('n', 2)]
# Count words
text = "one two three one two one"
word_count = Counter(text.split())
print(word_count) # Counter({'one': 3, 'two': 2, 'three': 1})
OrderedDict (Pre-Python 3.7)¶
In Python 3.7+, regular dicts maintain insertion order. OrderedDict is still useful for:
- Move-to-end operations
- LRU cache patterns
- Equality that considers order
from collections import OrderedDict
od = OrderedDict()
od["first"] = 1
od["second"] = 2
od["third"] = 3
od.move_to_end("first") # move 'first' to end
print(list(od.keys())) # ['second', 'third', 'first']
Complete Method Reference¶
| Method | Description |
|---|---|
d[key] |
Get value (KeyError if missing) |
d[key] = val |
Set value |
del d[key] |
Delete key (KeyError if missing) |
d.get(key[, def]) |
Get value, or def if missing |
d.setdefault(key[, def]) |
Get or set default |
d.pop(key[, def]) |
Remove & return value |
d.popitem() |
Remove & return last (key, value) |
d.update(other) |
Merge other dict |
d.clear() |
Remove all |
d.keys() |
View of all keys |
d.values() |
View of all values |
d.items() |
View of all (key, value) pairs |
key in d |
Membership test |
len(d) |
Number of items |
Practice Problems¶
# 1. Word frequency counter
def word_frequency(text):
words = text.lower().split()
freq = {}
for word in words:
freq[word] = freq.get(word, 0) + 1
return sorted(freq.items(), key=lambda x: x[1], reverse=True)
print(word_frequency("the cat sat on the mat the cat"))
# 2. Group anagrams
def group_anagrams(words):
groups = {}
for word in words:
key = tuple(sorted(word))
groups.setdefault(key, []).append(word)
return list(groups.values())
print(group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))
# 3. Phone book
phonebook = {}
while True:
cmd = input("add/find/quit: ").strip().lower()
if cmd == "quit":
break
elif cmd == "add":
name = input("Name: ")
number = input("Number: ")
phonebook[name] = number
elif cmd == "find":
name = input("Name: ")
print(phonebook.get(name, "Not found"))
# 4. Invert dictionary (handle duplicate values)
def invert_dict(d):
inverted = {}
for k, v in d.items():
inverted.setdefault(v, []).append(k)
return inverted
d = {"a": 1, "b": 2, "c": 1, "d": 3}
print(invert_dict(d)) # {1: ['a', 'c'], 2: ['b'], 3: ['d']}
Exercises: 05-04: Exercises — Dictionaries
⬅️ Previous: 05-03: Sets ➡️ Next: 05-05: Slicing — Extracting Subsequences