Skip to main content

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 O(1)O(1) on average; they are not a hard worst-case constant-time guarantee. Iteration is O(n)O(n).

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.

Source​

Explore connectionsOpen network