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

Что может быть ключом в словаре?

1.3 Junior🔥 91 комментариев
#Python и программирование

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

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

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

Ключи в словаре Python: полное руководство

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

Ключом может быть любой immutable (неизменяемый) тип данных:

  • int, float, str, tuple, bool, frozenset, None
  • НЕ может быть: list, dict, set (изменяемые типы)

Почему только immutable?

Основная идея: хеширование

Диктионарь в Python использует hash table для быстрого поиска:

# Когда ты пишешь
my_dict = {'key': 'value'}

# Под капотом Python делает:
hash_value = hash('key')  # вычисляет хеш строки
bucket = hash_value % table_size  # определяет позицию в таблице
my_dict[bucket] = value

Проблема с изменяемыми типами:

# Это НЕ сработает
my_list = [1, 2, 3]
my_dict = {my_list: 'value'}  # TypeError: unhashable type: 'list'

# Почему? Если список изменится, его хеш изменится
my_list[0] = 999  # теперь список другой
# Python не сможет найти 'value' в словаре

Примеры: что может быть ключом

1. Строки (str)

user_data = {
    'john_doe': {'age': 30, 'city': 'Moscow'},
    'jane_smith': {'age': 25, 'city': 'SPB'}
}

print(user_data['john_doe'])  # {'age': 30, 'city': 'Moscow'}

2. Числа (int, float)

# int как ключ
temp_by_hour = {
    0: -5.2,   # 00:00 - минус 5.2°
    1: -4.8,   # 01:00
    2: -3.1,   # 02:00
}

# float как ключ (редко, но возможно)
prices = {
    0.5: 'half',
    1.0: 'one',
    1.5: 'one and half'
}

print(prices[0.5])  # 'half'

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

Очень полезно для многомерных ключей:

# Координаты на карте
map_data = {
    (0, 0): 'start',
    (10, 10): 'end',
    (5, 5): 'checkpoint'
}

print(map_data[(5, 5)])  # 'checkpoint'

# Составной ключ: (город, улица, дом)
addresses = {
    ('Moscow', 'Red Square', 1): 'Kremlin',
    ('SPB', 'Nevsky', 25): 'Shop'
}

for (city, street, number), place in addresses.items():
    print(f"{city}, {street}, {number}: {place}")

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

feature_flags = {
    True: 'enabled',
    False: 'disabled'
}

print(feature_flags[True])  # 'enabled'

# На самом деле bool — это подкласс int
print(True == 1)   # True
print(False == 0)  # True

5. None

defaults = {
    None: 'undefined',
    'name': 'John',
    'age': 30
}

print(defaults[None])  # 'undefined'

6. frozenset

Неизменяемое множество:

# frozenset в качестве ключа
groups = {
    frozenset(['Alice', 'Bob']): 'Team A',
    frozenset(['Charlie', 'Diana']): 'Team B',
    frozenset(['Eve']): 'Solo'
}

team_key = frozenset(['Alice', 'Bob'])
print(groups[team_key])  # 'Team A'

Примеры: ЧТО НЕ может быть ключом

❌ Список (list)

# Ошибка
my_dict = {[1, 2, 3]: 'value'}
# TypeError: unhashable type: 'list'

# Почему нельзя?
my_list = [1, 2, 3]
my_list.append(4)  # изменили
# Теперь это другой список, хеш изменился

❌ Словарь (dict)

# Ошибка
my_dict = {{'key': 'val'}: 'value'}
# TypeError: unhashable type: 'dict'

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

# Ошибка
my_dict = {{1, 2, 3}: 'value'}
# TypeError: unhashable type: 'set'

# Решение: используй frozenset
my_dict = {frozenset([1, 2, 3]): 'value'}  # OK

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

Паттерн 1: Кэширование с tuple ключами

from functools import lru_cache

@lru_cache(maxsize=128)
def get_distance(point1, point2):
    """Вычисляем расстояние (кэшируется)"""
    x1, y1 = point1
    x2, y2 = point2
    return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5

# lru_cache требует hashable аргументы
result = get_distance((0, 0), (3, 4))  # 5.0
result = get_distance((0, 0), (3, 4))  # берется из кэша

Паттерн 2: Частотный анализ

# Подсчет частоты слов
from collections import Counter

words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
word_count = Counter(words)  # использует str как ключи

print(word_count)  # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(word_count['apple'])  # 3

Паттерн 3: Группировка по кортежу

# Анализ продаж по (месяц, товар)
sales = [
    {'month': 1, 'product': 'A', 'revenue': 1000},
    {'month': 1, 'product': 'B', 'revenue': 1500},
    {'month': 2, 'product': 'A', 'revenue': 1200},
    {'month': 2, 'product': 'B', 'revenue': 1800},
]

by_month_product = {}
for sale in sales:
    key = (sale['month'], sale['product'])
    by_month_product[key] = sale['revenue']

print(by_month_product[(1, 'A')])  # 1000
print(by_month_product[(2, 'B')])  # 1800

Паттерн 4: Кастомный класс как ключ

class User:
    def __init__(self, user_id, name):
        self.user_id = user_id
        self.name = name
    
    def __hash__(self):
        # Определяем как хешировать объект
        return hash(self.user_id)
    
    def __eq__(self, other):
        # Два пользователя равны если у них один ID
        return self.user_id == other.user_id

user1 = User(1, 'Alice')
user2 = User(1, 'Alice2')  # Тот же ID
user3 = User(2, 'Bob')

user_data = {user1: 'data1'}
user_data[user2] = 'data2'  # Перезапишет user1, т.к. они равны
user_data[user3] = 'data3'

print(len(user_data))  # 2 (user1 и user2 - это один ключ)

Производительность

import timeit

# Поиск по string ключу - O(1) в среднем
dict_str = {f'key_{i}': i for i in range(1000000)}
time_str = timeit.timeit(lambda: dict_str['key_500000'], number=1000000)

# Поиск по int ключу - еще быстрее
dict_int = {i: i for i in range(1000000)}
time_int = timeit.timeit(lambda: dict_int[500000], number=1000000)

print(f"String key: {time_str:.4f}s")
print(f"Int key: {time_int:.4f}s")
# Int ключи обычно быстрее

Важные замечания

1. Hash collision

# Два разных объекта могут иметь один hash
# Но у них разные __eq__, поэтому они разные ключи
key1 = 'abc'
key2 = 'abc'
print(key1 == key2)      # True (одинаковые строки)
print(hash(key1) == hash(key2))  # True (одинаковый хеш)
# В словаре это один и тот же ключ

my_dict = {key1: 'first'}
my_dict[key2] = 'second'  # Перезапишет first

2. Порядок в словаре (Python 3.7+)

# В Python 3.7+ словарь сохраняет порядок ключей
my_dict = {'z': 1, 'a': 2, 'm': 3}

for key in my_dict:
    print(key)  # z, a, m (в порядке вставки)

3. Когда tuple можно использовать вместо list

# Если нужны hashable данные
data = [
    (1, 'Alice', 30),
    (2, 'Bob', 25),
    (3, 'Charlie', 35)
]

# Можешь создать индекс
by_id = {person[0]: person for person in data}
print(by_id[1])  # (1, 'Alice', 30)

# Или использовать tuple как ключ
by_name_age = {(person[1], person[2]): person[0] for person in data}
print(by_name_age[('Alice', 30)])  # 1

Итоговая таблица

ТипМожно ли ключомПримечание
strСамый частый
intБыстрый поиск
floatРедко используется
tupleПолезна для составных ключей
boolНа самом деле это int
NoneДля значений по умолчанию
frozensetНеизменяемое множество
listИзменяемый тип
dictИзменяемый тип
setИзменяемый тип
пользовательский классЕсли определены hash и eq

Ключевой принцип: Ключ должен быть неизменяемым (immutable) и иметь стабильный хеш, который не меняется после создания объекта.

Что может быть ключом в словаре? | PrepBro