Dictionaries and Keyed State
A dict maps unique, hashable keys to values. It is mutable and preserves
insertion order, but that order is not sorted order.
counts = {"jpg": 10, "csv": 2}
counts["csv"] += 1
counts["txt"] = 4
Lookup contracts
Choose the operation that matches how absence should be handled:
required = config["region"] # KeyError if absent
optional = config.get("timeout", 30) # default if absent
if "token" in config: # membership checks keys
use(config["token"])
get cannot distinguish a missing key from a present key whose value equals the
default. Use membership testing or a private sentinel when that distinction
matters.
For accumulation, collections.defaultdict and collections.Counter often
state the intent better than repeated setdefault calls.
Keys and hashing
Keys must be hashable. Strings, numbers, and tuples of hashable values commonly qualify; lists and dictionaries do not. Objects that compare equal must have the same hash. Mutating data involved in equality or hashing after using an object as a key can make the mapping logically inconsistent.
Prefer domain identifiers with stable equality semantics. Do not use a mutable container merely because it can be wrapped in a custom hash implementation.
For the mechanisms behind these key rules, see hashing, collisions, and resizing.
Iteration and views
for key in mapping:
...
for key, value in mapping.items():
...
keys = mapping.keys() # dynamic view, not a copied list
values = mapping.values()
Changing the dictionary's size while iterating over a view can raise an error or
skip intended work. Iterate over list(mapping.items()) when a snapshot is
needed.
Updating and merging
update mutates the receiver. The | operator creates a new dictionary. In
both cases, values on the right win when keys overlap.
effective = defaults | overrides
defaults.update(overrides)
These are shallow operations: nested mappings are replaced, not recursively merged. Define an explicit policy for deep configuration merging.
For CPython's hash-table implementation, assuming constant-time hashing and equality, lookup, insertion, and deletion are typically on average; they are not a hard worst-case constant-time guarantee. Iteration is .
Equal keys, order, and independent defaults
The mapping contract
guarantees insertion order in Python 3.7 and later. Updating a value does not
move its key; deleting and reinserting a key places it last. Dictionary equality
compares key/value pairs, not insertion order. Equal numeric keys such as 1,
1.0, and True address the same entry.
mapping = {1: "integer", True: "boolean", 1.0: "float"}
assert len(mapping) == 1
assert mapping[1] == "float"
ordered = {"a": 1, "b": 2}
ordered["a"] = 3
assert list(ordered) == ["a", "b"]
assert ordered.pop("a") == 3
ordered["a"] = 4
assert list(ordered) == ["b", "a"]
assert ordered == {"a": 4, "b": 2}
shared = dict.fromkeys(["a", "b"], [])
shared["a"].append(1)
assert shared["b"] == [1]
separate = {key: [] for key in ["a", "b"]}
separate["a"].append(1)
assert separate["b"] == []
fromkeys reuses the same supplied value for every key. A comprehension evaluates
its value expression for each entry. A hash must stay stable during an object's
lifetime; equal hashes alone do not imply equal keys, because collisions are
resolved with equality checks. A tuple containing a list is not hashable.
del mapping[key] and pop(key) raise KeyError when absent; pop(key, default)
returns the default instead. Like get, its default expression is evaluated
before the method call, even if the key exists.