Dictionaries
Dicts map hashable keys to arbitrary values with average O(1) lookup. They are the default tool for records, indexes, caches, and dispatch tables in Python application code.
Recipe
defaults = {"retries": 3, "timeout": 30}
overrides = {"timeout": 60}
config = defaults | overrides
for key in config:
print(key, config[key])
if "retries" not in config:
config["retries"] = 1When to reach for this:
- JSON-like records and API payloads
- Counting and grouping (often with
Counter) - Memoization caches keyed by arguments
- String-to-handler dispatch tables
Working Example
from collections.abc import Callable
Handler = Callable[[dict], str]
def dispatch(handlers: dict[str, Handler], event: dict) -> str:
kind = str(event.get("type", "unknown"))
handler = handlers.get(kind, handlers["unknown"])
return handler(event)
def merge_records(base: dict[str, object], patch: dict[str, object]) -> dict[str, object]:
merged = base.copy()
merged.update(patch)
return merged
def invert_unique(mapping: dict[str, int]) -> dict[int, str]:
return {value: key for key, value in mapping.items()}
if __name__ == "__main__":
handlers: dict[str, Handler] = {
"user.created": lambda e: f"create {e['id']}",
"unknown": lambda e: "noop",
}
print(dispatch(handlers, {"type": "user.created", "id": 42}))
print(merge_records({"a": 1}, {"b": 2}))What this demonstrates:
handlers.get(kind, default)avoids KeyError for unknown eventsdict.copy+updateshallow-merges records- Dict comprehension inverts mapping when values are unique
- Callable values turn dicts into dispatch tables
Deep Dive
How It Works
- Hash table - Keys hashed to buckets; open addressing resolves collisions.
- Insertion order - Iteration order matches insert order (re-insert moves key to end in 3.7+).
- Views -
keys(),values(),items()are dynamic views, not snapshots. - Equality - Two dicts equal if same key-value pairs regardless of order.
- fromkeys -
dict.fromkeys(keys, default)builds dict with shared default caution.
Merge Options
| Syntax | Mutates left? |
|---|---|
a | b | No - new dict (3.9+) |
a.update(b) | Yes |
{**a, **b} | No - new dict |
Python Notes
# setdefault only when missing
counts: dict[str, int] = {}
counts.setdefault("a", 0)
counts["a"] += 1
# pop with default
value = config.pop("missing", None)Gotchas
- Mutable values shared via fromkeys -
dict.fromkeys(keys, [])shares one list. Fix: Comprehension with fresh mutable. - Iterating keys then deleting - RuntimeError if size changes during iteration. Fix:
list(d)snapshot first. - Using float NaN as key - NaN != NaN breaks uniqueness assumptions. Fix: Avoid or normalize.
- Large dict memory - Every key object stored. Fix: Consider DB, disk index, or
__slots__objects for records. - Relying on order for equality tests - Order differs across constructions. Fix: Compare as dicts, not key lists.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
TypedDict | Static shape for JSON dicts | Need methods or validation |
dataclass | Structured objects with behavior | Need JSON dict verbatim |
Pydantic model | Validated API models | Inner hot loop |
sqlite | Queryable persistent index | Tiny in-memory map |
FAQs
Are dict keys ordered?
Yes - insertion order is guaranteed. Reassigning existing key does not change position in 3.7+ CPython behavior for new insert only.
What makes a key hashable?
Immutable types with stable __hash__ - str, int, tuple of hashables. Mutable objects fail.
| vs update?
| returns new merged dict. update mutates in place - pick based on immutability needs.
How do I defaultdict?
collections.defaultdict or setdefault - defaultdict cleaner for grouping patterns.
Can I sort dict items?
sorted(d.items(), key=lambda kv: kv[1]) returns list of pairs - dict itself stays unordered conceptually but keeps insert order of reassigned dict.
What is ChainMap?
Search stack of dicts without copying - useful for scoped configuration overlays.
How deep copy a dict?
copy.deepcopy when nested mutables must be independent.
get vs []?
d[k] raises KeyError when missing. get returns None or default - safer for optional keys.
Dict comprehension vs loop?
{k: f(v) for k, v in items} idiomatic for transforms with optional if filter.
When not to use dict?
When you need columnar analytics (pandas/polars) or relational joins across many rows.
Related
- Sets & Frozensets - complementary uniqueness ops
- collections Module - defaultdict, Counter
- Immutability & Hashability - valid keys
- TypedDict & NamedTuple - typed records
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.