Dict keys
So far the keys have been fruit names, that is, strings. But a dict key is not always a string. Picture a board not at a stall but at a bus station: each row holds a route of two cities and the distance between them. The key here is a pair of names, and such a key is written as a tuple:
distances = {("Moscow", "Kazan"): 820, ("Kazan", "Perm"): 650}
print(distances[("Moscow", "Kazan")]) # 820
route = ["Kazan", "Perm"]
print(distances[tuple(route)]) # 650
print(("Perm", "Kazan") in distances) # FalseLet's go through it line by line:
distances[("Moscow", "Kazan")]finds the pair by the tuple key and gives820.- The list
routehad to be turned into a tuple with atuplecall: a list cannot be a key, and we'll see why below. ("Perm", "Kazan") in distancesgivesFalse: the elements are the same but in a different order, so it is a different key.
A tuple key is the usual way to build a key from several values without nested dicts.
Hashable keys
Picture a warehouse with thousands of boxes. The storekeeper does not look for a box by reading the labels one after another. They have a rule: the name on the label gives a shelf number, and they go straight to that shelf.
A dict works the same way. It computes a number from the key—a hash—and uses it to find the pair's place right away. So a dict does not compare the key you look for with all of its keys in turn. A read is equally fast in a small dict and in a large one.
The built-in hash function returns the hash: for example, hash(5) gives 5. The hash of a string is a large integer that changes from run to run, so nobody relies on its exact value.
While a key sits in a dict, its hash must not change. Otherwise it goes like a label rewritten after the box is already on the shelf. The rule leads from the new name to a different shelf, and the box cannot be found. The pair in the dict gets lost the same way.
That is why, among the built-in types, only immutable ones have a hash. They are called hashable:
- numbers, strings,
boolandNone; - tuples of such values.
A list and a dict can be changed in place, and they have no hash. A tuple that holds a list is not hashable either: hash(([1], 2)) raises the same error as hash([1]).
Interviewers like to ask this: "Why can't a list be a dict key?" The answer:
- a dict key must be hashable;
- a list is mutable, so it has no hash;
- for a compound key, use a tuple of immutable values.
1, 1.0 and True
Keys are compared by value, not by type. The numbers 1, 1.0 and True are equal and give the same hash, so in a dict they are one key:
d = {1: "int", 1.0: "float", True: "bool"}
print(d) # {1: 'bool'}
print(len(d), d[True]) # 1 boolLet's see what happened:
- The pair
1: "int"creates the key1. 1.0: "float"lands in the same row: the key stays1, and the value becomes'float'.True: "bool"again replaces only the value, and the dict ends up as{1: 'bool'}.
The string "1" is not equal to the number 1 and counts as a different key. This matters for computed keys: 2 / 2 gives 1.0, and a write under that key lands in the pair with the key 1.
Common mistake
A list is used as a key:
visits = {}
route = ["Moscow", "Kazan"]
print(len(visits)) # 0
visits[route] = 3 # ✗TypeError: unhashable type: 'list'The error happens when the line with the write runs, so the lines above it have already run, and 0 is printed. In Python 3.14 the message is longer but holds the same words unhashable type: 'list'. The fix is a tuple key: visits[tuple(route)] = 3.
Key points
- Only a hashable value can be a key: a number, a string,
bool,Noneor a tuple of them. - A dict finds a pair by the key's hash, so the key's hash must not change.
- A list cannot be a key: turn it into a tuple with
tuple. 1,1.0andTrueare equal, so in a dict they are one key.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.