Можно ли сделать изменяемый тип данных хешируемым в питоне?
Насколько я понял, чтобы сделать так, чтобы пользовательский тип данных мог быть ключем в словаре, он должен быть неизменяемым, хешируемым и уникальным, а также должен быть способен к сравнению. Для хешируемости можно переопределить метод __hash__, для сравнения __eq__. С неизменяемыми типами понятно, но есть ли какая-то возможность сделать так и для изменяемых типов?
Ответы (2 шт):
Вполне можно. Доказательство:
class C:
def __init__(self, x): # Простой `__init__`
self.x = x
def __hash__(self):
return hash(self.x)
# Есть `__hash__` и `__eq__`
def __eq__(self, other):
return self.x == other.x
def __repr__(self): # Метод `__repr__` для тестирования
return f'<class C with x {str(self.x)}>'
c = C(50) # Создаём экземпляр
d = {c: 1} # Загружаем его в словарь
print(d)
c.x = 70 # Меняем значение
print(d)
Вывод:
{<class C with x 50>: 1} # Начальное значение
{<class C with x 70>: 1} # Ключ изменился!
Но только подвох в том, что этот изменённый объект вы в словаре уже не найдёте:
# ... продолжение кода выше
print(d[c]) # `c` - это уже изменённый экземпляр
Вывод:
Traceback (most recent call last):
File "...", line 21, in <module>
print(d[c])
KeyError: <class C with x 70>
Это объясняется тем, что при изменении этого объекта его хэш тоже меняется:
>>> c = C(50)
>>> hash(c)
50
>>> c.x = 70 # Изменяем объект
>>> hash(c)
70 # Хэш изменился
Хэшируемым является только тот объект, который имеет _ hash _ метод и допускает сравнение с другими объектами (у него должен быть метод _ eq _) (Из книги Fluent Python Лусиану Рамальо).
Заметьте, ничего об изменяемости не сказано.
У пользовательских типов хешем является их айдишник (он по определению уникален), а _ eq _ просто сравнивает айдишники(унаследованный от object). Т.е. с пользовательскими типами вам не нужно делать ничего, чтобы они стали хэшируемыми. Как бы вы не меняли аттрибуты, айдишник (=хэш) никак не изменяется.
>>> class CustomClass:
... a = 1
...
>>> a_obj = CustomClass()
>>> hash(a_obj)
8753609441155
>>> d = {a_obj: 1}
>>> a_obj.a = 2
>>> hash(a_obj)
8753609441155
>>> d[a_obj]
1
Как вы видите изменение кастомных объектов не влияет на его хэш и на словарь, где он использовался.
В следующем я не уверен, но мне кажется так: В list например, хэш-функция напрямую зависит от содержимого списка (т.е. его изменение влияет на на значение хэш-функции) из-за особенностей реализации и поэтому он не является хэшируемым и поэтому не может быть ключом словарей и элментом set, frozenset