← Назад к вопросам
Что может быть ключом в словаре?
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) и иметь стабильный хеш, который не меняется после создания объекта.