← Назад к вопросам

Какие типы данных могут быть ключом в словаре?

1.0 Junior🔥 241 комментариев
#Python

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Типы данных, которые могут быть ключами в словаре (Dict)

В Python словарь может содержать ключи только неизменяемые типы данных (immutable). Это критическое требование для корректной работы хэшировки.

1. Правило: Ключ должен быть хешируемым

Для использования в качестве ключа объект должен быть хешируемым (hashable):

  • Иметь метод __hash__()
  • Быть неизменяемым (immutable)
  • Два равных объекта должны иметь одинаковый хэш
# Проверить, хешируемый ли объект
print(hash(5))           # OK: 5
print(hash("hello"))     # OK: 7598361297891167995
print(hash((1, 2)))      # OK: 1212408828307725699

# Это не хешируемо
print(hash([1, 2]))      # TypeError: unhashable type: 'list'
print(hash({"a": 1}))    # TypeError: unhashable type: 'dict'

2. Допустимые типы ключей

Числа (int, float, complex)

data = {}

# int
data[1] = "целое число"
data[100] = "большое число"

# float
data[3.14] = "пи"
data[2.71828] = "е"

# complex
data[1+2j] = "комплексное число"

print(data)
# {1: 'целое число', 100: 'большое число', 3.14: 'пи', 2.71828: 'е', (1+2j): 'комплексное число'}

# Практический пример
temperatures = {}
temperatures[36.6] = "нормальная"
temperatures[37.5] = "повышенная"
temperatures[39.0] = "высокая"

print(temperatures[37.5])  # повышенная

Строки (str)

# Самый частый вариант
user_data = {}

user_data["name"] = "Alice"
user_data["age"] = 25
user_data["email"] = "alice@example.com"

print(user_data["name"])  # Alice

# Многострочные строки тоже ОК
config = {
    "database_url": "postgresql://localhost:5432/mydb",
    "api_key": "secret_token_12345"
}

# Пустая строка — тоже валидный ключ
empty_key_dict = {"":"значение для пустой строки"}
print(empty_key_dict[""])  # значение для пустой строки

Кортежи (tuple)

Кортеж хешируемый, если все его элементы хешируемы:

# Кортежи как ключи — очень полезно
data = {}

# Координаты
data[(0, 0)] = "начало"
data[(1, 2)] = "точка A"
data[(5, -3)] = "точка B"

print(data[(1, 2)])  # точка A

# Дата
data[(2024, 3, 29)] = "сегодня"

# Вложенные кортежи
data[("user", "alice", "email")] = "alice@example.com"
data[("user", "bob", "age")] = 30

print(data[("user", "alice", "email")])  # alice@example.com

# Практический пример: граф рёбер
graph = {}
graph[("A", "B")] = 5   # Расстояние A→B
graph[("B", "C")] = 3
graph[("A", "C")] = 7

print(graph[("A", "B")])  # 5

Boolean (bool)

settings = {}

settings[True] = "включено"
settings[False] = "отключено"

print(settings[True])   # включено

# Практически всегда используется как конфиг
features = {
    True: ["feature_1", "feature_2"],
    False: ["disabled_feature"]
}

None

data = {}

data[None] = "нет значения"
data["key"] = "есть значение"

print(data[None])  # нет значения

# Практический пример: обработка отсутствующих данных
results = {
    None: "undefined",
    "success": "OK",
    "error": "ошибка"
}

Bytes

data = {}

# Байты хешируемы
data[b"hello"] = "строка в байтах"
data[b"\x00\x01"] = "бинарные данные"

print(data[b"hello"])  # строка в байтах

# Практический пример
binary_mappings = {
    b"\x89PNG": "PNG image",
    b"\xFF\xD8\xFF": "JPEG image",
    b"GIF8": "GIF image"
}

3. НЕ допустимые типы ключей

Списки (list)

data = {}

data[[1, 2, 3]] = "значение"  # TypeError: unhashable type: 'list'

# ПРАВИЛЬНО: использовать кортеж
data[(1, 2, 3)] = "значение"  # OK

Словари (dict)

data = {}

data[{"x": 1}] = "значение"  # TypeError: unhashable type: 'dict'

# Если нужен составной ключ, используйте кортеж
data[("x", 1)] = "значение"  # OK

Множества (set)

data = {}

data[{1, 2, 3}] = "значение"  # TypeError: unhashable type: 'set'

# frozenset — замороженное множество, оно хешируемо
data[frozenset([1, 2, 3])] = "значение"  # OK

Пользовательские классы (без hash)

class Person:
    def __init__(self, name):
        self.name = name

person = Person("Alice")
data = {}

data[person] = "информация"  # TypeError: unhashable type: 'Person'

4. Проблема с float в качестве ключей

Хотя float хешируемы, есть проблема с точностью:

data = {}

# Работает
data[0.1] = "первое"
data[0.1] = "второе"  # Перезапишет

print(data[0.1])  # второе (одна запись)

# ПРОБЛЕМА: неточность float
a = 0.1 + 0.2
b = 0.3

print(a == b)  # False! (из-за неточности float)

data[a] = "0.1 + 0.2"
data[b] = "0.3"

print(len(data))  # 3 (два разных ключа вместо одного!)
print(data[0.1 + 0.2])  # 0.1 + 0.2
print(data[0.3])        # 0.3

# РЕШЕНИЕ: использовать Decimal для точных чисел
from decimal import Decimal

precise_data = {}
precise_data[Decimal("0.1")] = "точное"
print(precise_data[Decimal("0.1")])  # точное

5. Хешируемые пользовательские классы

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):
        return self.x == other.x and self.y == other.y
    
    def __repr__(self):
        return f"Point({self.x}, {self.y})"

# Теперь можно использовать как ключ
points = {}
p1 = Point(1, 2)
p2 = Point(1, 2)  # Тот же Hash!
p3 = Point(5, 3)

points[p1] = "первая точка"
points[p3] = "третья точка"

print(points[p2])  # первая точка (p1 и p2 равны!)
print(len(points)) # 2

6. frozenset — множество как ключ

# frozenset — неизменяемое множество
data = {}

data[frozenset([1, 2, 3])] = "множество"
data[frozenset(["a", "b"])] = "буквы"

print(data[frozenset([1, 2, 3])])  # множество

# Практический пример: группы пользователей
groups = {
    frozenset(["alice", "bob"]): "team_1",
    frozenset(["charlie", "diana"]): "team_2"
}

print(groups[frozenset(["alice", "bob"])])  # team_1

7. Сравнительная таблица

ТипХешируемыйМожет быть ключомПримечание
intДаДаСамый быстрый
floatДаДаОпасно из-за неточности
strДаДаСамый частый
boolДаДаTrue/False
NoneДаДаЧасто используется
bytesДаДаДля бинарных данных
tupleДа (если элементы хешируемы)ДаОтличный вариант для составных ключей
frozensetДаДаНеизменяемое множество
listНетНетИспользуйте tuple вместо этого
dictНетНетИспользуйте tuple вместо этого
setНетНетИспользуйте frozenset вместо этого

8. Практические примеры

# Граф — словарь словарей
graph = {
    "A": {"B": 5, "C": 2},
    "B": {"A": 5, "C": 1},
    "C": {"A": 2, "B": 1}
}

# Кэш с кортежами как ключи
cache = {
    ("user", 1, "profile"): {"name": "Alice"},
    ("user", 2, "profile"): {"name": "Bob"}
}

# Счётчик уникальных пар
pairs = {}
pairs[("apple", "orange")] += 1
pairs[("apple", "banana")] += 1

# Группировка по типам
data_by_type = {}
data_by_type[int] = [1, 2, 3]
data_by_type[str] = ["a", "b", "c"]

Выводы

Ключи словаря должны быть хешируемыми:

Допустимые типы:

  • int, float, complex
  • str
  • tuple (если содержит только хешируемые объекты)
  • bool, None
  • bytes
  • frozenset

Недопустимые типы:

  • list (используйте tuple)
  • dict (используйте tuple)
  • set (используйте frozenset)

Лучшие практики:

  1. Для простых ключей: используйте int или str
  2. Для составных ключей: используйте tuple
  3. Для логических флагов: используйте bool или None
  4. Для множеств: используйте frozenset
  5. Избегайте float как ключи из-за неточности
  6. Для пользовательских объектов: реализуйте __hash__() и __eq__()
Какие типы данных могут быть ключом в словаре? | PrepBro