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

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

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

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

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

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

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

Это вопрос про ключи словарей в Python. Ключом (key) в словаре может быть только хешируемый объект — точно так же, как и элементы множества.

Основное правило

Объект может быть ключом словаря, если:

  1. Имеет метод __hash__(), возвращающий хеш
  2. Имеет метод __eq__() для сравнения на равенство
  3. Неизменяемый (immutable) — не меняется во время жизни

Это потому что Python использует хеш для быстрого поиска значения за O(1).

Объекты, которые МОГУТ быть ключами

1. Целые числа (int)

dict_int = {
    1: "one",
    2: "two",
    -5: "negative five",
    0: "zero"
}
print(dict_int[1])  # one
print(dict_int[-5]) # negative five

2. Вещественные числа (float)

dict_float = {
    1.5: "one and a half",
    2.71: "pi",
    0.0: "zero"
}
print(dict_float[1.5])  # one and a half

# Осторожно с точностью!
print({0.1 + 0.2: "sum"})  # Может быть проблема с precision

3. Строки (str)

user_profile = {
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30,
    "city": "Moscow"
}
print(user_profile["name"])  # Alice
print(user_profile["email"]) # alice@example.com

4. Кортежи (tuple)

Только если содержат хешируемые элементы:

# Правильно: кортеж с хешируемыми элементами
coordinates = {
    (0, 0): "origin",
    (1, 1): "diagonal",
    (10, 20): "far"
}
print(coordinates[(0, 0)])  # origin

# Вложенные кортежи
matrix = {
    (0, ("row", 0)): "first",
    (1, ("row", 1)): "second"
}
print(matrix[(0, ("row", 0))])  # first

# ОШИБКА: кортеж содержит список
try:
    bad_dict = {(1, [2, 3]): "value"}  # TypeError!
except TypeError as e:
    print(e)  # unhashable type: 'list'

5. Булевы значения (bool)

flags = {
    True: "enabled",
    False: "disabled"
}
print(flags[True])   # enabled
print(flags[False])  # disabled

# Важно: True == 1, False == 0
# Поэтому они перезаписывают друг друга
weird_dict = {1: "number", True: "bool"}
print(weird_dict)  # {1: "bool"} - bool перезаписал number

weird_dict2 = {0: "zero", False: "false"}
print(weird_dict2)  # {0: "false"} - false перезаписал zero

6. None

dict_with_none = {
    None: "null value",
    "key": "some value"
}
print(dict_with_none[None])  # null value

# Практическое использование
config = {
    "database": "localhost",
    None: "default value"
}
result = config.get("unknown_key", config[None])
print(result)  # default value

7. Frozenset

frozen_set_key = {
    frozenset({1, 2, 3}): "first set",
    frozenset({4, 5, 6}): "second set"
}
print(frozen_set_key[frozenset({1, 2, 3})])  # first set

# Практическое использование: уникальные комбинации
user_permissions = {
    frozenset({"read", "write"}): "editor",
    frozenset({"read"}): "viewer",
    frozenset({"read", "write", "admin"}): "admin"
}

8. Bytes

dict_bytes = {
    b"hello": "greeting",
    b"world": "planet"
}
print(dict_bytes[b"hello"])  # greeting

9. Пользовательские объекты с hash

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})"

point_map = {
    Point(0, 0): "origin",
    Point(1, 1): "diagonal",
    Point(10, 20): "far"
}

print(point_map[Point(0, 0)])  # origin
print(point_map[Point(1, 1)])  # diagonal

10. Enum

from enum import Enum

class Status(Enum):
    ACTIVE = 1
    INACTIVE = 2
    PENDING = 3

status_messages = {
    Status.ACTIVE: "Account is active",
    Status.INACTIVE: "Account is inactive",
    Status.PENDING: "Account is pending"
}

print(status_messages[Status.ACTIVE])  # Account is active

Объекты, которые НЕ могут быть ключами

1. Списки (list)

try:
    bad_dict = {[1, 2, 3]: "value"}  # TypeError!
except TypeError as e:
    print(e)  # unhashable type: 'list'

# Списки изменяемы
my_list = [1, 2, 3]
my_list[0] = 100  # можно менять

2. Словари (dict)

try:
    bad_dict = {{"a": 1}: "value"}  # TypeError!
except TypeError as e:
    print(e)  # unhashable type: 'dict'

# Словари изменяемы
my_dict = {"a": 1}
my_dict["b"] = 2  # можно менять

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

try:
    bad_dict = {{1, 2, 3}: "value"}  # TypeError!
except TypeError as e:
    print(e)  # unhashable type: 'set'

# Множества изменяемы
my_set = {1, 2, 3}
my_set.add(4)  # можно менять

# Решение: используйте frozenset
good_dict = {frozenset({1, 2, 3}): "value"}  # OK

4. Список в кортеже

try:
    bad_dict = {(1, [2, 3]): "value"}  # TypeError!
except TypeError as e:
    print(e)  # unhashable type: 'list'

# Правильно: только хешируемые элементы
good_dict = {(1, 2, 3): "value"}  # OK
good_dict2 = {(1, (2, 3)): "value"}  # OK - вложенный кортеж

Проверка хешируемости

def can_be_key(obj):
    """Проверяет, может ли объект быть ключом словаря"""
    try:
        hash(obj)
        return True
    except TypeError:
        return False

print(can_be_key(1))                    # True
print(can_be_key("hello"))              # True
print(can_be_key((1, 2, 3)))            # True
print(can_be_key([1, 2, 3]))            # False
print(can_be_key({"a": 1}))             # False
print(can_be_key({1, 2, 3}))            # False
print(can_be_key(frozenset({1, 2})))    # True

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

Кэширование результатов функции

def fib(n):
    cache = {}  # n - целое число, идеальный ключ
    
    def compute(n):
        if n in cache:
            return cache[n]
        
        if n <= 1:
            result = n
        else:
            result = compute(n-1) + compute(n-2)
        
        cache[n] = result
        return result
    
    return compute(n)

print(fib(10))  # 55

Координаты как ключи

# Игровая сетка
game_grid = {}

def set_tile(x, y, tile_type):
    game_grid[(x, y)] = tile_type

def get_tile(x, y):
    return game_grid.get((x, y), None)

set_tile(0, 0, "grass")
set_tile(1, 1, "water")
print(get_tile(0, 0))  # grass
print(get_tile(1, 1))  # water

Перечисления как ключи

from enum import Enum

class Role(Enum):
    ADMIN = 1
    USER = 2
    GUEST = 3

permissions = {
    Role.ADMIN: ["read", "write", "delete"],
    Role.USER: ["read", "write"],
    Role.GUEST: ["read"]
}

user_role = Role.USER
print(permissions[user_role])  # ["read", "write"]

Сравнение: dict vs set ключи

Исходя из того что мы знаем о множествах:

ТипМожет быть в setМожет быть ключом dict
intДаДа
floatДаДа
strДаДа
tupleДа (если хешируемы элементы)Да (если хешируемы элементы)
listНетНет
dictНетНет
setНетНет
frozensetДаДа
NoneДаДа
boolДаДа

Резюме

Ключом словаря может быть только хешируемый, неизменяемый объект:

Могут: int, float, str, bool, None, bytes, tuple (с хешируемыми элементами), frozenset, пользовательские объекты с __hash__()

Не могут: list, dict, set, bytearray, пользовательские объекты без __hash__()

Причина: Python использует хеш для O(1) поиска, поэтому ключ не должен меняться во время жизни словаря.

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