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

Ответ

В Python ключом словаря (dict) может быть любой хешируемый (hashable) тип данных. Хешируемость означает, что объект имеет неизменяемое значение, которое может быть использовано для вычисления уникального хеша.

Главное правило: Ключ должен быть неизменяемым (immutable) типом.

Допустимые типы ключей (хешируемые и неизменяемые):

  1. Числа: int, float, complex, bool (True/False).
  2. Строки: str.
  3. Кортежи (tuple): Но только если все элементы кортежа сами являются хешируемыми.
  4. frozenset: Неизменяемая версия множества.
  5. bytes.

Недопустимые типы ключей (нехешируемые и изменяемые):

  1. Списки (list).
  2. Словари (dict).
  3. Множества (set).
  4. Большинство пользовательских классов (по умолчанию хешируемы, но могут стать нехешируемыми, если реализуют __eq__ без __hash__ или являются изменяемыми).

Примеры на практике:

# ДОПУСТИМЫЕ ключи:
valid_dict = {
    42: 'integer key',
    3.14: 'float key',
    'name': 'string key',
    (1, 2, 3): 'tuple key',          # Кортеж из чисел
    (1, 'a', (2, 3)): 'complex tuple', # Кортеж с вложенным кортежем
    frozenset([1,2]): 'frozenset key',
    True: 'boolean key'
}
print(valid_dict[(1, 2, 3)])  # Вывод: tuple key

# НЕДОПУСТИМЫЕ ключи (вызовут TypeError: unhashable type):
try:
    invalid_dict = {
        [1, 2]: 'list key',   # Список - ИЗМЕНЯЕМЫЙ
        {1, 2}: 'set key',    # Множество - ИЗМЕНЯЕМОЕ
        {'a': 1}: 'dict key'  # Словарь - ИЗМЕНЯЕМЫЙ
    }
except TypeError as e:
    print(f'Ошибка: {e}')

Почему важна неизменяемость? Хеш ключа вычисляется один раз при его добавлении в словарь и используется для быстрого поиска значения. Если бы ключ мог измениться, его хеш также изменился бы, и найти связанное с ним значение стало бы невозможно, что нарушило бы целостность структуры данных.

Ответ 18+ 🔞

А, слушай, про ключи в питоновских словарях! Ну это, блядь, целая наука, но если по-простому, то запомни раз и навсегда, как мантру: ключ должен быть неизменяемым и хешируемым.

Что это значит на языке нормальных людей? А значит это, что ключ — это как отпечаток пальца, ёпта. Его вычислили один раз, запомнили, и всё, трогать его нельзя. Если он поменяется — пиши пропало, словарь его уже не узнает, и твоё значение нахуй потеряется в недрах оперативки.

Что можно пихать в ключи (или "что не сломается"):

  1. Циферки всякие: 42, 3.14, даже True с False (это ж те же единичка с ноликом по сути).
  2. Буквы (строки): 'привет', 'key' — их не изменишь, они как есть.
  3. Кортежи (tuple): Вот это хитрая жопа! Можно, но только если внутри кортежа тоже сидят святые, то есть другие неизменяемые штуки. (1, 2, 'a') — ок. (1, [2,3]) — уже пиздец, потому что внутри спишок изменяемый засел.
  4. frozenset: Это такой замороженный set, его не разморозишь, не изменишь — идеальный кандидат.
  5. bytes: Ну, байты, чё.

Что НЕЛЬЗЯ пихать в ключи (или "что вызовет TypeError: unhashable type и все засмеются"):

  1. Списки (list): О, нет-нет-нет! Сегодня [1, 2], завтра append(3) — и всё, ключ поехал, словарь в ауте.
  2. Словари (dict): Да ты что, блядь! Сам словарь хочет стать ключом в другом словаре? Цирк с конями! Они же изменяемые, как мартышлюшки.
  3. Множества (set): Та же история — добавил элемент, и хеш поменялся. Нахуй так нельзя.
  4. Твои кастомные классы: По умолчанию-то можно, но если ты начнёшь там __eq__ городить без __hash__, или класс твой будет меняться как погода в Питере — всё, приехали, нехешируемый.

Смотри, как на практике:

# Всё работает, все довольны
normalny_dict = {
    42: 'ключ-число',
    3.14: 'ключ с плавающей запятой, блядь',
    'имя': 'строковый ключ',
    (1, 2, 3): 'кортежный ключ', # Кортеж из циферок
    (1, 'а', (2, 3)): 'сложный кортеж', # Внутри ещё кортеж сидит
    frozenset([1,2]): 'ключ из заморозки',
    True: 'булевый ключ'
}
print(normalny_dict[(1, 2, 3)])  # Напечатает: кортежный ключ

# А вот тут сейчас будет магия и пиздец
try:
    govnokod_dict = {
        [1, 2]: 'ключ-список',   # СПИСОК! ИЗМЕНЯЕМЫЙ! НЕ-НЕ-НЕ!
        {1, 2}: 'ключ-множество', # МНОЖЕСТВО! ТОЖЕ ИЗМЕНЯЕМОЕ!
        {'a': 1}: 'ключ-словарь'  # СЛОВАРЬ В КЛЮЧЕ СЛОВАРЯ, ЁПТА!
    }
except TypeError as e:
    print(f'Ошибка, как и ожидалось: {e}') # И получишь свои законные грабли по лбу

А почему, спрашивается, такая хуйня? Да потому что словарь — это умная структура, она по хешу ключа мгновенно находит твоё значение. Представь, ты пришёл в гардероб, сдал пальто (ключ 'хуй в пальто'), тебе дали номерок (хеш). А потом ты это пальто (ключ) перешил в куртку. Приходишь с номерком, а куртку тебе не отдают — "не, мужик, у нас тут пальто числится". Вот и словарь так же: изменился ключ — хеш не совпал, значение потерял. Поэтому только неизменяемое, блядь, только хардкор!