Python Dictionaries: get vs Brackets, Merging and Views
A Python dictionary maps hashable keys to values with O(1) lookup, in insertion order. When to use get or setdefault, how to merge dicts, and safe iteration.
- Course: Python study plan
- Module: Dictionaries and sets
- Kind: Lesson
- Reading time: 14 min
- Runtime: CPython 3.11
What is a dictionary in Python?
A dictionary, dict, is Python's mapping from hashable keys to values, with O(1) average lookup, insertion and deletion. Since Python 3.7 it keeps keys in insertion order. d[k] raises KeyError for a missing key, d.get(k, default) returns a default instead, and k in d tests whether a key is present.
Lesson
The dictionary is the data structure Python itself is built on — module namespaces, object attributes, keyword arguments are all dicts — and it is the one you will reach for most often: a mapping from keys to values with constant-time lookup, insertion and deletion, keeping the order in which keys were added. This lesson covers construction, the access methods and which one to use when a key may be missing, the views, iteration, updating and merging, dict comprehensions, and the rule against changing a dict's size while iterating it.
Building
empty = {}
ages = {"ada": 36, "grace": 45}
from_pairs = dict([("a", 1), ("b", 2)])
from_zip = dict(zip(names, scores))
from_kwargs = dict(x=1, y=2) # keys must be identifiers
defaults = dict.fromkeys(["a", "b"], 0) # {'a': 0, 'b': 0} — one shared value object
squares = {n: n * n for n in range(4)} # comprehension
Keys must be hashable — immutable, in practice: strings, numbers, tuples of hashables, frozensets (lesson 4). A list as a key is TypeError: unhashable type: 'list'. Values can be anything. Duplicate keys in a literal keep the last value.
Reading
ages["ada"] # 36
ages["bob"] # KeyError: 'bob'
ages.get("bob") # None — no exception
ages.get("bob", 0) # 0 — a default
"bob" in ages # False — the membership test is on keys, O(1)
len(ages) # 2
d[k] when the key must exist — its KeyError is the right failure for a bug. d.get(k, default) when absence is normal. k in d when only presence matters. The idiom if k in d: v = d[k] does two lookups where get does one; the idiom d.get(k) or default is the truthiness trap from Module 2 (a stored 0 or "" is replaced).
Writing
ages["bob"] = 30 # insert or overwrite
ages["ada"] += 1 # read, add, write back — KeyError if absent
del ages["bob"] # KeyError if absent
ages.pop("bob") # remove and return; KeyError if absent
ages.pop("bob", None) # remove and return, or the default
ages.popitem() # remove and return the *last* inserted (key, value)
ages.setdefault("cy", 0) # insert 0 only if absent; return the value either way
ages.update({"dee": 1}, ed=2) # merge in pairs from a mapping / iterable / keywords
ages.clear()
setdefault is the one-line "insert if missing, then use": d.setdefault(key, []).append(x) groups values under a key without an if. The next lesson replaces that with defaultdict, which is clearer when every key gets the same kind of default.
Merging
merged = {**a, **b} # 3.5: a new dict; b's values win on shared keys
merged = a | b # 3.9: the same, as an operator
a |= b # update a in place
a.update(b) # the same as |=
Merges are left-to-right: later mappings overwrite earlier ones.
Views and iteration
for k in d: # keys, in insertion order
for k in d.keys(): # the same, explicit
for v in d.values():
for k, v in d.items(): # pairs — the usual loop
list(d) # the keys as a list
sorted(d) # keys sorted
sorted(d.items(), key=lambda kv: kv[1], reverse=True) # pairs by value, descending
max(d, key=d.get) # the key with the largest value
keys(), values() and items() return views: live windows onto the dict that reflect later changes, support len and in, and — for keys and items — behave like sets (a.keys() & b.keys() is the common keys). They are not lists: index them with list(d.values())[0], or next(iter(d)) for the first key. Since 3.7 insertion order is guaranteed by the language, so a dict doubles as an ordered record of arrival; reversed(d) iterates the keys backwards (3.8).
Changing size during iteration
for k in d:
if bad(k):
del d[k] # RuntimeError: dictionary changed size during iteration
Iterate over a copy of the keys (for k in list(d):), or build a new dict with a comprehension ({k: v for k, v in d.items() if not bad(k)}). Changing a value during iteration (d[k] = … for an existing k) is fine.
Nested dictionaries
users = {"ada": {"age": 36, "langs": ["python"]}}
users["ada"]["langs"].append("c")
users.setdefault("bob", {})["age"] = 30
users.get("cy", {}).get("age") # None — safe navigation with defaults
Two-level access is common enough that the pattern d.get(k1, {}).get(k2) is worth knowing, and defaultdict(dict) (next lesson) removes the setdefault. JSON documents arrive as nested dicts and lists (lesson 5).
Dicts as records and as tables
A dict with fixed, known keys ({"name": …, "age": …}) is a record — a lighter dataclass (Module 8), fine for JSON and quick scripts. A dict with keys that vary with the data (counts[word]) is a table — the lookup structure. Both are dicts, but the first is usually better as a class once it has behaviour, and the second is where get, setdefault and the counting idioms live.
Pitfalls
d[k]wherekmay be absent;getorin.d.get(k) or defaultwhen the stored value can be falsy.- A list as a key.
dict.fromkeys(keys, [])— one shared list for every key.- Deleting from a dict while iterating it.
- Assuming
d.keys()is a list (d.keys()[0]is aTypeError).
Key takeaways
- Dicts map hashable keys to any values with O(1) lookup and insertion, in insertion order.
d[k]raisesKeyError,d.get(k, default)does not,k in dtests presence;setdefaultinserts a default and returns it.pop,del,popitem,update,|and|=change or merge; later mappings win.keys()/values()/items()are live views; iterateitems()for pairs; sort with a key onitems().- Never change a dict's size while iterating it; iterate
list(d)or build a new dict.
Common questions
What is the difference between d[key] and d.get(key) in Python?
d[key] raises KeyError when the key is missing, the right failure when it must exist. d.get(key) returns None, or the default passed as its second argument, so use it when absence is normal. Avoid d.get(k) or default: it also replaces a stored 0 or empty string.
How do I merge two dictionaries in Python?
a | b (Python 3.9+) or {**a, **b} builds a new dict, and a |= b or a.update(b) merges in place. Merges run left to right, so on a shared key the value from b wins.
How do I fix RuntimeError: dictionary changed size during iteration?
The error comes from adding or deleting keys while a loop iterates the dict. Loop over a copy of the keys, for k in list(d):, or build a new dict with a comprehension that keeps only the wanted items. Changing the value of an existing key during iteration is fine.
Are Python dictionaries ordered?
Yes. Since Python 3.7 the language guarantees that a dict keeps its keys in insertion order, so iteration and the keys(), values() and items() views follow it. popitem() removes the last inserted pair, and reversed(d) walks the keys backwards.
How do I sort a dictionary by value in Python?
Sort its items with a key that picks the value: sorted(d.items(), key=lambda kv: kv[1], reverse=True) returns the pairs highest value first. max(d, key=d.get) gives the single key with the largest value.
Exercises
Phone book
Keep a phone book from commands read until the end of input: add name number stores or replaces; find name prints the number or not found; remove name prints removed or not found; list prints every entry as name: number in name order. Use get for lookups, pop with a default for removal, and never raise KeyError.
Input: commands. Output: one line per find, remove and per entry of list.
add ada 100
add bob 200
find ada
find cy
remove bob
remove bob
list
prints
100
not found
removed
not found
ada: 100Merge with overrides
The first line holds default settings as key=value pairs, the second the user's settings in the same form (either may be empty). Merge them with the | operator so the user's values win, print the merged settings in the order the operator produces (defaults' order, then new user keys), and then the keys whose value the user changed, sorted.
Input: two lines. Output: key=value per merged entry, then overridden: <keys> or overridden: none.
theme=light size=12 lang=en
size=14 lang=en font=mono
prints
theme=light
size=14
lang=en
font=mono
overridden: sizeIn this module: Dictionaries and sets
- Dictionaries — the mapping at the centre of Python (this lesson)
- Counting and grouping — Counter, defaultdict and the accumulation idioms
- Sets — membership, deduplication and set algebra
- Hashing and keys — what makes an object usable in a dict or set
- Nested data and JSON
- Choosing a collection — the complexity table and heapq
- Checkpoint — Dictionaries and sets
← Checkpoint — Lists, tuples and sequences · Counting and grouping — Counter, defaultdict and the accumulation idioms →