← Назад к вопросам
Какие типы данных подходят как ключ словаря в Python?
1.3 Junior🔥 161 комментариев
#Python
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие типы данных подходят как ключ словаря в Python?
Ключи словаря в Python должны быть хешируемыми (hashable) и неизменяемыми (immutable). Это очень важное ограничение, которое обеспечивает правильную работу хеш-таблиц.
Правило: Хешируемость
Объект хешируемый, если:
- Он неизменяем (immutable)
- Имеет метод
__hash__() - Его хеш-значение не меняется во время жизни объекта
- Два равных объекта имеют одинаковый хеш
import hashlib
# Хешируемый объект
print("Хешируемые объекты:")
print(f" hash(5) = {hash(5)}")
print(f" hash('hello') = {hash('hello')}")
print(f" hash((1, 2, 3)) = {hash((1, 2, 3))}")
# Нехешируемый объект
try:
print(f" hash([1, 2, 3]) = {hash([1, 2, 3])}")
except TypeError as e:
print(f" hash([1, 2, 3]) → TypeError: {e}")
ПОДХОДЯЩИЕ типы (Хешируемые)
1. int (целые числа)
data = {}
data[5] = "five"
data[100] = "one hundred"
data[-42] = "negative forty-two"
data[0] = "zero"
print("int как ключи:")
for key, value in data.items():
print(f" {key} (type={type(key).__name__}): {value}")
# Особенность: большие числа имеют одинаковый хеш
print(f"\nhash(256) = {hash(256)}")
print(f"hash(257) = {hash(257)}")
print(f"hash(1) = {hash(1)}")
print(f"hash(258) = {hash(258)}")
# В Python: для небольших целых чисел (-5 до 256) хешируются как сами числа
# Для больших чисел используется специальная функция
2. str (строки)
users = {}
users["alice"] = 30
users["bob"] = 25
users["charlie"] = 35
print("str как ключи:")
for key, value in users.items():
print(f" {key}: {value} лет")
# Важно: строки чувствительны к регистру
users["Alice"] = 31 # Другой ключ!
print(f"\n'alice' != 'Alice': {users['alice']} vs {users['Alice']}")
# Пустая строка тоже валидный ключ
empty_dict = {"": "value for empty string"}
print(f"Пустая строка как ключ: {empty_dict['']}")
3. float (числа с плавающей точкой)
temperatures = {}
temperatures[36.6] = "normal"
temperatures[37.5] = "fever"
temperatures[35.2] = "hypothermia"
print("float как ключи:")
for temp, status in temperatures.items():
print(f" {temp}°C: {status}")
# ОСТОРОЖНО: float имеет проблемы с точностью
data = {0.1 + 0.2: "value"} # 0.30000000000000004
print(f"\n0.1 + 0.2 = {0.1 + 0.2}")
print(f"0.3 = {0.3}")
print(f"Равны ли? {0.1 + 0.2 == 0.3}")
print(f"Но hash одинаков? {hash(0.1 + 0.2) == hash(0.3)}")
print(f"Поэтому ключ {0.1 + 0.2} найдется по {0.3}")
try:
print(data[0.3]) # Найдёт значение!
except KeyError:
print("Ключ не найден")
4. tuple (кортежи)
ВАЖНО: кортеж хешируемый ТОЛЬКО если все его элементы хешируемы!
data = {}
# Простые кортежи
data[(1, 2)] = "coordinates"
data[("x", "y")] = "axes"
data[(1, "mixed", 3.14)] = "mixed types"
print("tuple как ключи:")
for key, value in data.items():
print(f" {key}: {value}")
# Вложенные кортежи (если все элементы хешируемы)
data[(1, (2, 3))] = "nested tuple"
print(f"\nВложенный кортеж: {data[(1, (2, 3))]}")
# Кортеж с изменяемым элементом — ОШИБКА!
try:
bad_key = (1, [2, 3]) # Список внутри кортежа
data[bad_key] = "won't work"
except TypeError as e:
print(f"\nОшибка: {e}")
print("Причина: список ([2, 3]) — это изменяемый объект!")
# Пустой кортеж тоже хешируемый
data[()] = "empty tuple"
print(f"\nПустой кортеж: {data[()]}")
5. bool (логические значения)
flags = {}
flags[True] = "on"
flags[False] = "off"
print("bool как ключи:")
for key, value in flags.items():
print(f" {key}: {value}")
# Интересный факт: True и 1 имеют одинаковый хеш!
print(f"\nhash(True) = {hash(True)}")
print(f"hash(1) = {hash(1)}")
print(f"hash(False) = {hash(False)}")
print(f"hash(0) = {hash(0)}")
# Поэтому True и 1 — ОДИН И ТОТ ЖЕ КЛЮЧ!
mixed = {True: "boolean", 1: "integer"}
print(f"\n{True}: {mixed}")
print("True и 1 конфликтуют как ключи!")
6. None
data = {None: "no value", "key1": "value1"}
print("None как ключ:")
print(f" {data}")
print(f" None: {data[None]}")
# Часто используется для значений по умолчанию
defaults = {
None: "default",
"user_id": 123,
"status": "active"
}
7. frozenset (неизменяемые множества)
# frozenset — неизменяемая версия set
data = {}
fs1 = frozenset([1, 2, 3])
fs2 = frozenset(['a', 'b'])
data[fs1] = "numbers"
data[fs2] = "letters"
print("frozenset как ключи:")
for key, value in data.items():
print(f" {key}: {value}")
# Особенность: порядок не важен
fs_alt = frozenset([3, 1, 2])
print(f"\nfs1 == fs_alt: {fs1 == fs_alt}")
print(f"hash(fs1) == hash(fs_alt): {hash(fs1) == hash(fs_alt)}")
print(f"Найдёт ключ: {data[fs_alt]}")
8. Пользовательские типы (custom classes)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
# Определяем хеш на основе координат
return hash((self.x, self.y))
def __eq__(self, other):
# Два Point равны, если координаты одинаковые
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __repr__(self):
return f"Point({self.x}, {self.y})"
data = {}
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
data[p1] = "first point"
data[p3] = "third point"
print("Custom class как ключ:")
for key, value in data.items():
print(f" {key}: {value}")
# p1 и p2 равны и имеют одинаковый хеш!
print(f"\np1 == p2: {p1 == p2}")
print(f"hash(p1) == hash(p2): {hash(p1) == hash(p2)}")
print(f"Найдёт значение: {data[p2]}")
НЕ ПОДХОДЯЩИЕ типы (Нехешируемые)
1. list (список) — ОШИБКА!
data = {}
try:
data[[1, 2, 3]] = "value"
except TypeError as e:
print(f"list как ключ → {e}")
# Причина: list изменяемый
my_list = [1, 2, 3]
my_list[0] = 999 # Можем изменить!
print(f"Изменили список на {my_list}")
print("Если бы это был ключ, нарушилась бы целостность словаря!")
2. set (множество) — ОШИБКА!
try:
data = {{1, 2, 3}: "value"} # set как ключ
except TypeError as e:
print(f"set как ключ → {e}")
# Используй frozenset вместо set!
data = {frozenset([1, 2, 3]): "value"} # Работает!
3. dict (словарь) — ОШИБКА!
try:
data = {{'a': 1, 'b': 2}: "value"} # dict как ключ
except TypeError as e:
print(f"dict как ключ → {e}")
# Словарь изменяемый, поэтому не подходит
Проверка хешируемости
def is_hashable(obj):
"""Проверяет, хешируемый ли объект"""
try:
hash(obj)
return True
except TypeError:
return False
print("Проверка хешируемости:")
for obj in [5, "str", [1, 2], (1, 2), {1, 2}, {1: 2}, None, True]:
print(f" {str(obj):20} → {is_hashable(obj)}")
Рекомендации
Лучшие практики:
- ✓ Используй str для словарей (наиболее часто)
- ✓ Используй int для индексирования
- ✓ Используй tuple для комбинированных ключей
- ✓ Используй frozenset для коллекций как ключи
- ✓ Определи
__hash__и__eq__для пользовательских классов
Избегай:
- ✗ list как ключ (используй tuple)
- ✗ set как ключ (используй frozenset)
- ✗ dict как ключ
- ✗ изменяемые объекты
- ✗ float как ключ (проблемы с точностью)
Вывод: Ключи словаря должны быть хешируемыми (неизменяемыми). Основные подходящие типы: int, str, tuple, frozenset, None и пользовательские классы с __hash__.