Можно ли сделать изменяемый тип данных хешируемым в питоне?

Рейтинг: 2Ответов: 2Опубликовано: 11.06.2023

Насколько я понял, чтобы сделать так, чтобы пользовательский тип данных мог быть ключем в словаре, он должен быть неизменяемым, хешируемым и уникальным, а также должен быть способен к сравнению. Для хешируемости можно переопределить метод __hash__, для сравнения __eq__. С неизменяемыми типами понятно, но есть ли какая-то возможность сделать так и для изменяемых типов?

Ответы

▲ 1

Вполне можно. Доказательство:

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            # Хэш изменился
▲ 0

Хэшируемым является только тот объект, который имеет _ 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