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

Какие типы данных подходят как ключ словаря в Python?

1.3 Junior🔥 161 комментариев
#Python

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

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

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

Какие типы данных подходят как ключ словаря в Python?

Ключи словаря в Python должны быть хешируемыми (hashable) и неизменяемыми (immutable). Это очень важное ограничение, которое обеспечивает правильную работу хеш-таблиц.

Правило: Хешируемость

Объект хешируемый, если:

  1. Он неизменяем (immutable)
  2. Имеет метод __hash__()
  3. Его хеш-значение не меняется во время жизни объекта
  4. Два равных объекта имеют одинаковый хеш
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__.

Какие типы данных подходят как ключ словаря в Python? | PrepBro