Чем отличаются неизменяемые и хэшируемые типы данных в Python?

Ответ

Неизменяемый (immutable) тип — это объект, состояние которого нельзя изменить после его создания. Примеры: int, str, tuple, frozenset.

Хэшируемый (hashable) тип — это объект, который имеет хэш-значение, не меняющееся на протяжении его жизни. Такие объекты могут быть ключами в словарях (dict) и элементами множеств (set).

Ключевое различие:

Все хэшируемые объекты неизменяемы, но не все неизменяемые объекты хэшируемы.

Это связано с тем, что хэш-значение объекта вычисляется на основе его содержимого. Если бы содержимое могло измениться (т.е. объект был бы изменяемым), его хэш тоже бы изменился, что нарушило бы работу словарей и множеств.

Классический пример нехэшируемого, но неизменяемого объекта — кортеж, содержащий изменяемый элемент (например, список).

Пример:

# Строка - неизменяемая и хэшируемая
my_dict = {"hello": 1}
print(hash("hello"))  # Работает

# Кортеж с неизменяемыми элементами - неизменяемый и хэшируемый
tuple_1 = (1, 2, 3)
my_set = {tuple_1}
print(hash(tuple_1))  # Работает

# Кортеж с изменяемым элементом (списком) - неизменяемый, но НЕ хэшируемый
tuple_2 = (1, [2, 3])
try:
    hash(tuple_2)
except TypeError as e:
    print(e)  # Вывод: unhashable type: 'list'

Ответ 18+ 🔞

А, ну вот, смотри, опять этот вечный вопрос про неизменяемое и хэшируемое. Люди путаются, как слепые котята в коробке с болтами, блядь.

Короче, слушай сюда, раз и навсегда.

Неизменяемый (immutable) — это как каменная скрижаль, высеченная в граните. Создал — и всё, нихуя не поменяешь. int, str, tuple — вот это всё. Попробуй изменить строку — получишь новую, а старая как была, так и осталась лежать, сука, в памяти.

Хэшируемый (hashable) — это уже про другое. Это про то, что у объекта есть свой хеш — этакая цифровая отпечатка пальца, которая не меняется за всю его гребаную жизнь. И благодаря этой фигне его можно сунуть в dict как ключ или в set как элемент.

А теперь, внимание, главный подвох, от которого у всех мозг вскипает, как молоко на плите:

Все хэшируемые объекты — неизменяемы. Это железно. Но! Не все неизменяемые объекты — хэшируемы.

Почему, спросишь? Да потому что хеш считается по содержимому, ёпта! Если б объект мог меняться, то и хеш бы прыгал, как блоха на собачьей свадьбе. И тогда вся система словарей и множеств накрылась бы медным тазом — ключ бы пропал, ищи-свищи.

И вот тебе классический пиздец, который всех валит с ног: кортеж (tuple). Сам по себе — неизменяемый, святое дело. Но если в него запихнуть какой-нибудь изменяемый пиздюк, например, список, то весь кортеж сразу становится нехэшируемым. Потому что список внутри может измениться, а кортеж-то формально неизменяем — он не может запретить списку внутри себя мутировать. Вот такая, блядь, хитрая жопа получается.

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

# Строка — святая корова, неизменяемая и хэшируемая. Всё чинно, благородно.
my_dict = {"привет": 100}
print(hash("привет"))  # Выдаст число, всё ок

# Кортеж из чисел — тоже святыня. Неизменяем и хэшируем.
tuple_1 = (42, 3.14, 9000)
my_set = {tuple_1}  # Запихнули в множество — красота!
print(hash(tuple_1))  # Работает, как швейцарские часы

# А теперь, держись за стул. Кортеж, но внутри него — ГРЕБАНЫЙ СПИСОК!
tuple_2 = (1, [2, 3])
try:
    hash(tuple_2)  # Пытаемся получить хеш...
except TypeError as e:
    print(e)  # И получаем по ебалу: unhashable type: 'list'

Вот и весь сказ, блядь. Кортеж-то вроде как каменный, но внутри у него живая, шевелящаяся хрень — список. И Python такой: «Ну нахуй, я не могу дать тебе постоянный хеш, потому что эта твоя внутренняя срань завтра может стать другой». И он прав, сука.

Так что запомни: хэшируемость — это не просто про «нельзя изменить». Это про гарантию, что ничего внутри никогда не пошевелится. А если шевелится — то это уже не ключ для словаря, а так, просто кусок данных, с которым можно делать что угодно, кроме как использовать там, где нужен порядок и стабильность, ебать.