🎯 Learning Objectives
- Create dictionaries and understand key-value structure
- Access, add, update, and delete entries safely
- Use essential methods:
get(),keys(),values(),items(),setdefault(),update() - Iterate over dictionaries in multiple ways
- Build nested dictionaries and work with real-world data shapes
- Use dictionary comprehensions for concise dict creation
What is a Dictionary?
A dictionary (dict) is an unordered* collection of
key-value pairs. Think of it like a real dictionary: you look up a
word (key) to find its definition (value).
*As of Python 3.7+, dicts maintain insertion order, but they're not indexed by position.
# Creating dictionaries
person = {
"name": "Alice",
"age": 30,
"city": "London"
}
# Other ways to create
empty = {}
from_constructor = dict(name="Bob", age=25)
from_pairs = dict([("x", 1), ("y", 2)])
# Keys must be hashable (immutable): str, int, float, tuple, bool
# Values can be anything
config = {
"debug": True,
"max_retries": 3,
"endpoints": ["/api/users", "/api/posts"],
(0, 0): "origin" # tuple as key — valid!
}
create_dicts.py
Accessing Values
person = {"name": "Alice", "age": 30, "city": "London"}
# Bracket notation — raises KeyError if key doesn't exist
print(person["name"]) # "Alice"
# print(person["email"]) → KeyError: 'email'
# .get() — returns None (or a default) if key is missing
print(person.get("email")) # None
print(person.get("email", "N/A")) # "N/A"
print(person.get("name", "N/A")) # "Alice" (key exists)
# Check if a key exists
print("name" in person) # True
print("email" in person) # False
print("Alice" in person) # False — 'in' checks KEYS, not values
accessing.py
.get(key, default) when a key might be missing and you want a fallback.
Use dict[key] when the key must exist — a KeyError is a useful signal
that something is wrong.
Adding & Updating
person = {"name": "Alice", "age": 30}
# Add a new key-value pair
person["email"] = "alice@example.com"
# Update an existing value
person["age"] = 31
# update() — merge another dict (overwrites existing keys)
person.update({"city": "Paris", "age": 32})
print(person)
# {'name': 'Alice', 'age': 32, 'email': 'alice@example.com', 'city': 'Paris'}
# setdefault() — set only if key doesn't exist
person.setdefault("country", "France") # adds "country": "France"
person.setdefault("name", "Unknown") # does nothing — "name" already exists
print(person["name"]) # "Alice"
# Merge with | operator (Python 3.9+)
defaults = {"theme": "dark", "lang": "en"}
overrides = {"lang": "fr", "font_size": 14}
merged = defaults | overrides
print(merged) # {'theme': 'dark', 'lang': 'fr', 'font_size': 14}
modifying.py
Removing Entries
| Method | Action | Returns |
|---|---|---|
del dict[key] | Remove key (KeyError if missing) | — |
.pop(key) | Remove & return value (KeyError if missing) | The value |
.pop(key, default) | Remove & return value (default if missing) | Value or default |
.popitem() | Remove & return last inserted pair | (key, value) tuple |
.clear() | Remove all entries | None |
d = {"a": 1, "b": 2, "c": 3, "d": 4}
# del
del d["a"] # d = {"b": 2, "c": 3, "d": 4}
# pop with default (safe)
val = d.pop("z", 0) # 0 (key didn't exist, no error)
val = d.pop("b") # 2, d = {"c": 3, "d": 4}
# popitem (LIFO in 3.7+)
last = d.popitem() # ("d", 4), d = {"c": 3}
removing.py
Key Methods Reference
| Method | Returns |
|---|---|
.keys() | View of all keys |
.values() | View of all values |
.items() | View of all (key, value) pairs |
.get(key, default) | Value for key, or default |
.setdefault(key, default) | Value for key; sets it if missing |
.update(other) | None (merges other into dict) |
.copy() | Shallow copy of the dict |
len(d) | Number of key-value pairs |
person = {"name": "Alice", "age": 30, "city": "London"}
print(list(person.keys())) # ['name', 'age', 'city']
print(list(person.values())) # ['Alice', 30, 'London']
print(list(person.items())) # [('name', 'Alice'), ('age', 30), ('city', 'London')]
print(len(person)) # 3
methods.py
Iterating Over Dictionaries
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
# Iterate over keys (default)
for name in scores:
print(name)
# Iterate over values
for score in scores.values():
print(score)
# Iterate over key-value pairs (most common)
for name, score in scores.items():
print(f"{name}: {score}")
# Sorted iteration
for name in sorted(scores):
print(f"{name}: {scores[name]}")
# Sort by value
for name, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
print(f"{name}: {score}")
# Bob: 92, Alice: 85, Charlie: 78
iteration.py
.items() when you need both key and value. Iterating with
for k in dict: and then accessing dict[k] works but is
less readable and slightly slower.
Dictionary Comprehensions
Like list comprehensions, but produce a dictionary:
# Basic: {key_expr: value_expr for item in iterable}
squares = {n: n ** 2 for n in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# With condition
even_squares = {n: n ** 2 for n in range(1, 11) if n % 2 == 0}
print(even_squares) # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
# Transform existing dict
prices = {"apple": 1.20, "banana": 0.50, "cherry": 2.00}
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted) # {'apple': 1.08, 'banana': 0.45, 'cherry': 1.8}
# Swap keys and values
flipped = {v: k for k, v in prices.items()}
print(flipped) # {1.2: 'apple', 0.5: 'banana', 2.0: 'cherry'}
# From two lists
keys = ["name", "age", "city"]
values = ["Alice", 30, "London"]
person = dict(zip(keys, values))
print(person) # {'name': 'Alice', 'age': 30, 'city': 'London'}
dict_comp.py
Nested Dictionaries
Dictionaries often contain other dictionaries — this is how structured data (like JSON) is represented in Python:
users = {
"alice": {
"email": "alice@example.com",
"age": 30,
"roles": ["admin", "editor"]
},
"bob": {
"email": "bob@example.com",
"age": 25,
"roles": ["viewer"]
}
}
# Access nested values
print(users["alice"]["email"]) # alice@example.com
print(users["bob"]["roles"][0]) # viewer
# Safe nested access with .get()
phone = users["alice"].get("phone", "not provided")
print(phone) # "not provided"
# Add to nested structure
users["alice"]["phone"] = "+44 7700 123456"
# Iterate nested
for username, profile in users.items():
print(f"{username}: {profile['email']} ({', '.join(profile['roles'])})")
nested.py
glom. In production code you'll often
validate structure with Pydantic (Lesson 38).
Common Dict Patterns
# Counting occurrences
text = "the cat sat on the mat"
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
print(word_count) # {'the': 2, 'cat': 1, 'sat': 1, 'on': 1, 'mat': 1}
# Better: use collections.Counter
from collections import Counter
word_count = Counter(text.split())
print(word_count.most_common(2)) # [('the', 2), ('cat', 1)]
# Grouping
students = [("Alice", "A"), ("Bob", "B"), ("Charlie", "A"), ("Dave", "B")]
groups = {}
for name, grade in students:
groups.setdefault(grade, []).append(name)
print(groups) # {'A': ['Alice', 'Charlie'], 'B': ['Bob', 'Dave']}
# Better: use collections.defaultdict
from collections import defaultdict
groups = defaultdict(list)
for name, grade in students:
groups[grade].append(name)
# Merging multiple dicts (Python 3.9+)
a = {"x": 1}
b = {"y": 2}
c = {"z": 3}
merged = a | b | c # {'x': 1, 'y': 2, 'z': 3}
patterns.py
Primary sources: Python Docs — dict · Python Docs — collections
Ask your AI tutor! Not sure when to use .get() vs
bracket access? Want to know how defaultdict works internally?
Dictionaries are everywhere in Python — solid understanding here pays off hugely.
💻 Exercises
Write a script that takes the string
"to be or not to be that is the question" and builds a dictionary
mapping each word to how many times it appears. Print the result sorted by count
(highest first).
Show solution
text = "to be or not to be that is the question"
counts = {}
for word in text.split():
counts[word] = counts.get(word, 0) + 1
# Sort by count descending
for word, n in sorted(counts.items(), key=lambda x: x[1], reverse=True):
print(f"{word}: {n}")
# to: 2
# be: 2
# or: 1 ... etc.
Create a phonebook dict with at least 5 entries (name → phone number). Write code that: looks up a name (handling the case where it's not found), adds a new entry, deletes an entry, and prints all contacts alphabetically.
Show solution
phonebook = {
"Alice": "555-0101",
"Bob": "555-0102",
"Charlie": "555-0103",
"Dave": "555-0104",
"Eve": "555-0105"
}
# Look up (safe)
name = "Frank"
number = phonebook.get(name, "Not found")
print(f"{name}: {number}") # Frank: Not found
# Add
phonebook["Frank"] = "555-0106"
# Delete
del phonebook["Dave"]
# Print alphabetically
for name in sorted(phonebook):
print(f" {name}: {phonebook[name]}")
Given {"a": 1, "b": 2, "c": 3}, create a new dictionary where the
keys and values are swapped: {1: "a", 2: "b", 3: "c"}.
Use a dictionary comprehension. Then think: what happens if two keys have the
same value?
Show solution
original = {"a": 1, "b": 2, "c": 3}
# Simple inversion (assumes unique values)
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
# If values aren't unique, collect keys in a list
data = {"a": 1, "b": 2, "c": 1}
inverted_safe = {}
for k, v in data.items():
inverted_safe.setdefault(v, []).append(k)
print(inverted_safe) # {1: ['a', 'c'], 2: ['b']}