Why this matters
Lists find things by position. Very often you want to find things by name: a user's record by username, a word's count by the word, a configuration value by its key, the price of a product by its code. The dictionary (dict) maps keys to values and finds a value by its key in roughly constant time, no matter how big the dictionary is. Dicts are everywhere in Python: JSON data becomes dicts, keyword arguments arrive as a dict, and even the interpreter stores each module's global variables in one.
Creating and using dicts
A dict literal lists key: value pairs in curly braces:
ages = {"ada": 36, "alan": 41, "grace": 85}
empty = {}
print(ages["alan"])
print(len(ages))
41
3
Assigning to a key adds or replaces:
ages["linus"] = 54 # new key: added
ages["ada"] = 37 # existing key: value replaced
print(ages)
{'ada': 37, 'alan': 41, 'grace': 85, 'linus': 54}
Each key appears at most once. Other ways to build dicts:
d1 = dict(red=1, green=2) # keyword arguments
d2 = dict([("x", 10), ("y", 20)]) # from (key, value) pairs
print(d1, d2)
{'red': 1, 'green': 2} {'x': 10, 'y': 20}
Missing keys
Looking up a key that is not there with square brackets raises KeyError:
print(ages["bob"])
Traceback (most recent call last):
File "demo.py", line 1, in <module>
print(ages["bob"])
~~~~^^^^^^^
KeyError: 'bob'
Three ways to deal with that:
print("bob" in ages) # membership tests keys: False
print(ages.get("bob")) # None instead of an error
print(ages.get("bob", 0)) # or a default of your choice: 0
in on a dict checks keys, not values. To test values use x in d.values() (which is a slow, linear scan).
Removing
ages = {"ada": 36, "alan": 41, "grace": 85}
del ages["alan"] # KeyError if missing
born = ages.pop("grace") # remove and return the value
maybe = ages.pop("bob", None) # with a default, no error
print(ages, born, maybe)
{'ada': 36} 85 None
Iterating
Looping over a dict gives its keys. The methods keys(), values() and items() give views of keys, values, and (key, value) pairs:
prices = {"tea": 3.5, "cake": 4.0, "jam": 2.25}
for item in prices:
print(item)
for item, price in prices.items():
print(f"{item}: {price:.2f}")
print(sum(prices.values()))
tea
cake
jam
tea: 3.50
cake: 4.00
jam: 2.25
9.75
Since Python 3.7, dicts preserve insertion order: iteration visits keys in the order they were first added (replacing a value keeps the position; deleting and re-adding moves it to the end). That is a language guarantee, so output that loops over a dict you built is deterministic. If you want a different order, sort: for k in sorted(d): gives keys alphabetically.
Warning: Do not add or delete keys while looping over a dict. Python detects it and raises
RuntimeError: dictionary changed size during iteration. Loop overlist(d)(a copy of the keys) if you must delete.
Patterns you will use constantly
Counting. Start at zero for unseen keys:
counts = {}
for ch in "mississippi":
counts[ch] = counts.get(ch, 0) + 1
print(counts)
{'m': 1, 'i': 4, 's': 4, 'p': 2}
Grouping. Collect items into lists per key. setdefault(key, default) returns the existing value, or inserts default and returns it, so it is perfect for "create the list on first use":
words = ["apple", "avocado", "banana", "blueberry", "cherry"]
by_letter = {}
for w in words:
by_letter.setdefault(w[0], []).append(w)
print(by_letter)
{'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
The standard library also offers collections.Counter and collections.defaultdict for these two jobs; module 10 covers them. Write them by hand first so you know what they do.
Merging. d.update(other) copies all pairs from other into d, overwriting duplicates; d | other makes a new merged dict:
defaults = {"color": "blue", "size": "M"}
chosen = {"size": "L"}
print(defaults | chosen)
{'color': 'blue', 'size': 'L'}
Records. A dict can describe one thing with named fields: {"name": "Ada", "born": 1815}. A list of such dicts is how JSON data usually looks.
What happens underneath: a hash table
How can d[key] be fast with a million entries? A dict is a hash table. When you store a key, Python calls hash(key), which turns the key into a large integer, and uses that number to pick a slot in an internal array. To look the key up later, it hashes again, goes straight to that slot, and checks that the key there is equal (==) to the one you asked for:
hash("alan") -> 7203947190... -> slot 5
slots: 0 [ ]
1 [ "ada" -> 36 ]
...
5 [ "alan" -> 41 ]
No scanning through all the entries is needed, so lookup, insertion and deletion take about the same time whatever the size: O(1) on average. (When two keys land on the same slot, a collision, Python probes other slots in a fixed pattern; the internals module goes into the details, and into how CPython keeps insertion order cheaply.)
This design explains the rule on keys: keys must be hashable, meaning their hash never changes during their lifetime. If a key changed after insertion, its hash would change, and Python would look in the wrong slot. So immutable built-in types are hashable (str, int, float, bool, None, tuples of hashable items) and mutable ones are not:
d = {[1, 2]: "pair"}
TypeError: unhashable type: 'list'
Use a tuple instead: d[(1, 2)] = "pair". Values have no restrictions at all.
Note that string hashes are randomized per process (a security measure), so hash("alan") differs between runs. This never affects which keys a dict contains, or its iteration order, which follows insertion.
Common mistakes
KeyErroron lookups of keys that may be missing: useget,in, orsetdefault.- Expecting
into search values: it checks keys. - Using a list as a key:
TypeError: unhashable type: 'list'. Convert to a tuple. - Overwriting by accident: assigning to an existing key silently replaces its value; duplicate keys in a literal keep the last one.
- Changing size during iteration:
RuntimeError. Iterate over a copy of the keys. - Comparing numeric strings: values read from input are strings; convert them before arithmetic or comparisons.
Recap
- A dict maps hashable keys to arbitrary values:
{"k": v},d[k],d[k] = v,del d[k]. getandinhandle missing keys;popremoves and returns;setdefaultandget(k, 0) + 1power grouping and counting.- Iteration follows insertion order; use
items()for pairs andsorted(d)for alphabetical keys. - A dict is a hash table: average O(1) operations, and keys must be hashable (immutable).
Going further: read "Dictionaries" in the official tutorial (section 5.5). Then try print(globals()) at the top level of a script: the module's namespace is just a dict.