Ответ
Неизменяемость (immutability) и хэшируемость (hashability) — это разные, но часто связанные свойства объектов в Python.
- Неизменяемый объект — это объект, состояние которого нельзя изменить после создания. При любой "операции изменения" создается новый объект. Примеры:
int,float,str,tuple,frozenset. - Хэшируемый объект — это объект, который имеет хэш-значение (целое число), неизменяемое на протяжении его жизни, и может корректно сравниваться с другими объектами (имеет метод
__eq__). Хэшируемые объекты могут быть ключами в словарях (dict) и элементами множеств (set).
Ключевая связь и различие:
- Почти все встроенные неизменяемые типы хэшируемы.
- Изменяемые типы (списки
list, словариdict, множестваset) — нехэшируемы. - Хэшируемость не гарантирует неизменяемость. Можно создать пользовательский изменяемый класс и определить для него метод
__hash__, но это считается антипаттерном, так как изменение объекта после помещения в словарь нарушит структуру данных.
Примеры:
# 1. Неизменяемый и хэшируемый объект (строка)
my_string = "hello"
my_dict = {my_string: "value"} # Допустимо, строка хэшируема
print(hash(my_string)) # Выведет хэш-значение
# 2. Изменяемый и нехэшируемый объект (список)
my_list = [1, 2, 3]
try:
my_dict = {my_list: "value"} # Вызовет TypeError
except TypeError as e:
print(e) # unhashable type: 'list'
# 3. Кортеж (tuple) хэшируем, только если все его элементы хэшируемы.
tuple_hashable = (1, "two", 3.0)
print(hash(tuple_hashable)) # Работает
tuple_unhashable = (1, [2, 3]) # Содержит изменяемый список
try:
hash(tuple_unhashable) # Вызовет TypeError
except TypeError as e:
print(e) # unhashable type: 'list'
Для тестирования важно понимать эти различия, например, при использовании фикстур pytest или данных в параметризованных тестах, где в качестве ключей параметризации могут использоваться только хэшируемые значения.
Ответ 18+ 🔞
А, ну слушай, вот есть такая тема, которая мозги начинающим выносит напрочь. Неизменяемость и хэшируемость. Это, ёпта, как близнецы, но не братья — похожи, а суть разная. Сейчас разжуем.
Неизменяемый объект (immutable) — это как памятник, блядь. Поставили и всё. Тронуть нельзя. Ну то есть если ты попытаешься его «изменить», тебе просто новый памятник отольют, а старый так и будет стоять. int, str, tuple — они такие, заскорузлые. Создали и забыли.
Хэшируемый объект (hashable) — это уже про другое. Это объект, которому можно выдать паспорт — хэш (это просто число, да). И этот паспорт на всю его жизнь один и тот же. И с таким паспортом его уже можно в особые места пускать — делать ключом в словаре или запихивать в множество (set). Главное правило — паспорт не должен меняться, и личность (значение) должна чётко проверяться (через __eq__).
А теперь, блядь, где собака зарыта:
- Почти все встроенные неизменяемые типы — они и хэшируемые. Логично, раз памятник стоит вечно, то и паспорт у него вечный. Это как
int,str,tuple(если внутри тоже всё неподвижно, об этом ниже). - Все изменяемые типы (
list,dict,set) — нихера не хэшируемые. Ну представь, ты даёшь списку паспорт, а он потом вырастает или сжимается — пиздец, паспорт недействителен, а он уже в словаре ключом записан. Полный бардак, поэтому Python сразу говорит: «Нет, сука, так нельзя». - Вот тут самое интересное: хэшируемость НЕ гарантирует неизменяемость! Технически-то можно взять свой класс, сделать его изменяемым, но написать для него метод
__hash__. Но это, ядрёна вошь, чистой воды самоубийство и антипаттерн. Сделаешь так — получишь словарь, который накроется медным тазом, потому что объект изменится, а паспорт-то старый. Доверия к такому коду — ебать ноль.
Примеры, чтобы вообще всё встало на свои места:
# 1. Классика: строка. Неизменяемая и хэшируемая. Всё пучком.
my_string = "hello"
my_dict = {my_string: "value"} # Можно, паспорт есть
print(hash(my_string)) # Смотри-ка, номер паспорта
# 2. Пиздец и хаос: список. Изменяемый и нехэшируемый.
my_list = [1, 2, 3]
try:
my_dict = {my_list: "value"} # Пытаемся впихнуть невпихуемое
except TypeError as e:
print(e) # Тебе вежливо скажут: "unhashable type: 'list'". Иди на хуй со своим списком.
# 3. Кортеж — хитрая жопа. Сам по себе неизменяемый, но хэшируемым будет только если ВСЁ внутри него тоже хэшируемо.
tuple_hashable = (1, "two", 3.0) # Внутри только камни-памятники
print(hash(tuple_hashable)) # Работает, паспорт выдан.
tuple_unhashable = (1, [2, 3]) # А тут внутри сидит живой, шевелящийся список!
try:
hash(tuple_unhashable) # Пытаемся выдать паспорт на всю конструкцию
except TypeError as e:
print(e) # И опять: "unhashable type: 'list'". Весь кортеж из-за одной сволочи внутри стал нелегалом.
В тестировании это, блядь, важно понимать. Вот в pytest параметризацию делаешь — туда можно передавать в качестве идентификаторов или значений только хэшируемую фигню. Сунешь список — получишь по шапке. Так что держи ухо востро, чувак.