Ключи словаря
До сих пор ключами были названия фруктов, то есть строки. Но ключом словаря бывает не только строка. Представьте табличку не на прилавке, а на автостанции: в каждой строке маршрут из двух городов и расстояние между ними. Ключ здесь — пара названий, и такой ключ записывают кортежем:
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) # FalseДавайте разберём по строкам:
distances[("Moscow", "Kazan")]находит пару по ключу-кортежу и даёт820.- Список
routeпришлось превратить в кортеж вызовомtuple: список ключом быть не может, и ниже мы разберём почему. ("Perm", "Kazan") in distancesдаётFalse: элементы те же, но в другом порядке, а значит, это другой ключ.
Кортеж в ключе — обычный способ составить ключ из нескольких значений без вложенных словарей.
Хешируемые ключи
Представьте склад, где ящиков тысячи. Кладовщик не ищет нужный ящик, читая этикетки подряд. У него есть правило: из названия на этикетке получается номер полки, и он сразу идёт к ней.
Словарь работает так же. Он вычисляет из ключа число — хеш — и по нему сразу находит место пары. Поэтому словарь не сравнивает искомый ключ со всеми своими ключами по очереди. Чтение одинаково быстро и в маленьком словаре, и в большом.
Хеш возвращает встроенная функция hash: например, hash(5) даёт 5. Хеш строки — большое целое число, которое меняется от запуска к запуску, поэтому на конкретное значение не опираются.
Пока ключ лежит в словаре, его хеш не должен меняться. Иначе выйдет как с этикеткой, которую переписали, когда ящик уже стоял на полке. По новому названию правило ведёт к другой полке, и ящик не найти. Так и пара в словаре потеряется.
Поэтому из встроенных типов хеш есть только у неизменяемых. Их называют хешируемыми:
- числа, строки,
boolиNone; - кортежи из таких значений.
Список и словарь можно изменить на месте, и хеша у них нет. Кортеж, внутри которого лежит список, тоже не хешируем: hash(([1], 2)) вызывает ту же ошибку, что и hash([1]).
Этот вопрос часто задают на собеседовании: «Почему список не может быть ключом словаря?» Ответ:
- ключ словаря должен быть хешируемым;
- список изменяемый, поэтому хеша у него нет;
- нужен составной ключ — берут кортеж из неизменяемых значений.
1, 1.0 и True
Ключи сравниваются по значению, а не по типу. Числа 1, 1.0 и True равны и дают одинаковый хеш, поэтому в словаре это один ключ:
d = {1: "int", 1.0: "float", True: "bool"}
print(d) # {1: 'bool'}
print(len(d), d[True]) # 1 boolРазберём, что произошло:
- Пара
1: "int"создаёт ключ1. 1.0: "float"попадает в ту же строку: ключ остаётся1, а значение становится'float'.True: "bool"снова заменяет только значение, и в словаре остаётся{1: 'bool'}.
Строка "1" с числом 1 не равна и считается другим ключом. Это важно, когда ключи получаются вычислением: 2 / 2 даёт 1.0, и запись под таким ключом попадёт в пару с ключом 1.
Частая ошибка
Ключом сделали список:
visits = {}
route = ["Moscow", "Kazan"]
print(len(visits)) # 0
visits[route] = 3 # ✗TypeError: unhashable type: 'list'Ошибка возникает при выполнении строки с записью, поэтому строки выше неё успевают выполниться, и 0 печатается. В Python 3.14 сообщение длиннее, но в нём те же слова unhashable type: 'list'. Исправление — ключ-кортеж: visits[tuple(route)] = 3.
Главное
- Ключом может быть только хешируемое значение: число, строка,
bool,Noneили кортеж из них. - Словарь находит пару по хешу ключа, поэтому хеш ключа не должен меняться.
- Список ключом быть не может: превратите его в кортеж через
tuple. 1,1.0иTrueравны, поэтому в словаре это один ключ.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.