> Какие есть ограничения на ключи в dict в Python? (Python)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Домклик

Стек: Python

> Пример ответа

Короткий ответ

Ключи dict в Python должны быть хэшируемыми (hashable) и поддерживать сравнение на равенство. Это означает, что ключом может быть любой неизменяемый объект: числа, строки, кортежи (если их элементы хэшируемы), frozenset. Изменяемые типы - list, dict, set - использовать нельзя, так как их hash меняется при мутации, что ломает инварианты хеш-таблицы. Также ключи должны быть уникальными в рамках одного словаря.

Подробное объяснение

Основное ограничение - требование хэшируемости. В CPython это проверяется наличием метода __hash__ у объекта. Если объект определяет __eq__, но не определяет __hash__, он становится нехэшируемым (Python автоматически устанавливает __hash__ = None). Это защита от ситуации, когда два равных объекта имеют разные хеши.

Второе ограничение - неизменяемость. Даже если пользовательский класс определяет __hash__, но его атрибуты можно менять, такой объект формально хэшируем, но использовать его как ключ опасно: после мутации он перестанет находиться в словаре. Python не может это предотвратить на уровне языка, поэтому это ограничение скорее практическое.

Третье ограничение - уникальность. При вставке ключа Python сравнивает хеши, а при коллизии - сами объекты через ==. Если два разных объекта равны, второй перезапишет первый. Это важно для пользовательских классов с переопределённым __eq__.

Четвёртое ограничение - порядок. Начиная с Python 3.7, dict сохраняет порядок вставки, но это не гарантия сортировки. Ключи не должны полагаться на порядок при сравнении.

На практике

В реальном коде чаще всего используют строки, числа и кортежи. Если нужен составной ключ - кортеж из неизменяемых элементов. Для множеств ключей - frozenset. Пользовательские классы как ключи - редкость, но возможны, если чётко контролировать неизменяемость.

Практическое правило: если объект можно изменить после создания, он не должен быть ключом. Даже если технически это работает, это источник трудноуловимых багов.

Пример кода

PYTHON
# Корректные ключи
d = {
42: "int",
"key": "str",
(1, 2): "tuple",
frozenset({1, 2}): "frozenset",
}
# Некорректно - list не хэшируем
# d[[1, 2]] = "error" # TypeError: unhashable type: 'list'
# Некорректно - dict не хэшируем
# d[{"a": 1}] = "error" # TypeError: unhashable type: 'dict'
# Опасный случай - изменяемый класс
class MutableKey:
def __init__(self, value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
return self.value == other.value
k = MutableKey(1)
d = {k: "original"}
k.value = 2 # ключ "потерян"
print(d) # {<__main__.MutableKey object at 0x...>: 'original'}
print(d.get(k)) # None - объект не находится

Как отвечать на собеседовании

Начни с главного - хэшируемость и неизменяемость. Затем поясни, почему это важно: хеш-таблица требует стабильного хеша на время жизни ключа. Упомяни, что кортеж хэшируем только если все его элементы хэшируемы. Если спросят про пользовательские классы - скажи, что они могут быть ключами, но нужно гарантировать неизменяемость атрибутов, участвующих в __hash__ и __eq__.

Для senior-уровня добавь нюанс: в CPython строки кэшируют хеш, а числа - нет, но это деталь реализации. Также можно упомянуть, что None и Ellipsis тоже хэшируемы.

Что проверяет интервьюер

Интервьюер проверяет понимание внутреннего устройства dict: хеш-таблица, коллизии, сравнение при равенстве хешей. Также проверяет знание граничных случаев: кортеж с list внутри не хэшируем, mutable-объект с __hash__ - ловушка. Для senior важно показать, что ты понимаешь не только формальные требования, но и практические риски.

Типичные ошибки

  • Утверждение, что ключом может быть любой неизменяемый объект - неверно, нужен именно хэшируемый.
  • Забывают, что кортеж с изменяемым элементом не хэшируем.
  • Путают уникальность ключей с уникальностью хешей - хеши могут совпадать (коллизии), но ключи должны быть уникальны через ==.
  • Считают, что порядок ключей гарантирован всегда - это верно только с Python 3.7+.
  • Предлагают использовать list как ключ, обернув в tuple - это работает, но нужно помнить, что tuple с list внутри всё равно не хэшируем.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью