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