Перейти к содержимому
Теория · Словари и множества · урок 4 из 11

Ключи словаря

До сих пор ключами были названия фруктов, то есть строки. Но ключом словаря бывает не только строка. Представьте табличку не на прилавке, а на автостанции: в каждой строке маршрут из двух городов и расстояние между ними. Ключ здесь — пара названий, и такой ключ записывают кортежем:

Python
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

Давайте разберём по строкам:

  1. distances[("Moscow", "Kazan")] находит пару по ключу-кортежу и даёт 820.
  2. Список route пришлось превратить в кортеж вызовом tuple: список ключом быть не может, и ниже мы разберём почему.
  3. ("Perm", "Kazan") in distances даёт False: элементы те же, но в другом порядке, а значит, это другой ключ.

Кортеж в ключе — обычный способ составить ключ из нескольких значений без вложенных словарей.

Хешируемые ключи

Представьте склад, где ящиков тысячи. Кладовщик не ищет нужный ящик, читая этикетки подряд. У него есть правило: из названия на этикетке получается номер полки, и он сразу идёт к ней.

Словарь работает так же. Он вычисляет из ключа число — хеш — и по нему сразу находит место пары. Поэтому словарь не сравнивает искомый ключ со всеми своими ключами по очереди. Чтение одинаково быстро и в маленьком словаре, и в большом.

Хеш возвращает встроенная функция hash: например, hash(5) даёт 5. Хеш строки — большое целое число, которое меняется от запуска к запуску, поэтому на конкретное значение не опираются.

Пока ключ лежит в словаре, его хеш не должен меняться. Иначе выйдет как с этикеткой, которую переписали, когда ящик уже стоял на полке. По новому названию правило ведёт к другой полке, и ящик не найти. Так и пара в словаре потеряется.

Поэтому из встроенных типов хеш есть только у неизменяемых. Их называют хешируемыми:

  • числа, строки, bool и None;
  • кортежи из таких значений.

Список и словарь можно изменить на месте, и хеша у них нет. Кортеж, внутри которого лежит список, тоже не хешируем: hash(([1], 2)) вызывает ту же ошибку, что и hash([1]).

Этот вопрос часто задают на собеседовании: «Почему список не может быть ключом словаря?» Ответ:

  • ключ словаря должен быть хешируемым;
  • список изменяемый, поэтому хеша у него нет;
  • нужен составной ключ — берут кортеж из неизменяемых значений.

1, 1.0 и True

Ключи сравниваются по значению, а не по типу. Числа 1, 1.0 и True равны и дают одинаковый хеш, поэтому в словаре это один ключ:

Python
d = {1: "int", 1.0: "float", True: "bool"}
print(d)  # {1: 'bool'}
print(len(d), d[True])  # 1 bool

Разберём, что произошло:

  1. Пара 1: "int" создаёт ключ 1.
  2. 1.0: "float" попадает в ту же строку: ключ остаётся 1, а значение становится 'float'.
  3. True: "bool" снова заменяет только значение, и в словаре остаётся {1: 'bool'}.

Строка "1" с числом 1 не равна и считается другим ключом. Это важно, когда ключи получаются вычислением: 2 / 2 даёт 1.0, и запись под таким ключом попадёт в пару с ключом 1.

Частая ошибка

Ключом сделали список:

Python
visits = {}
route = ["Moscow", "Kazan"]
print(len(visits))  # 0
visits[route] = 3  # ✗
TEXT
TypeError: unhashable type: 'list'

Ошибка возникает при выполнении строки с записью, поэтому строки выше неё успевают выполниться, и 0 печатается. В Python 3.14 сообщение длиннее, но в нём те же слова unhashable type: 'list'. Исправление — ключ-кортеж: visits[tuple(route)] = 3.

Главное

  • Ключом может быть только хешируемое значение: число, строка, bool, None или кортеж из них.
  • Словарь находит пару по хешу ключа, поэтому хеш ключа не должен меняться.
  • Список ключом быть не может: превратите его в кортеж через tuple.
  • 1, 1.0 и True равны, поэтому в словаре это один ключ.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом