Может ли изменяться хеш-функция объекта во время его существования в Python?

«Может ли изменяться хеш-функция объекта во время его существования в Python?» — вопрос из категории Python, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, хеш объекта (значение, возвращаемое методом __hash__) должен оставаться постоянным на протяжении всего времени его жизни. Это критически важное правило для корректной работы хеш-таблиц, лежащих в основе словарей (dict), множеств (set) и frozenset. Если хеш изменится после помещения объекта в такую коллекцию, он станет недостижим.

Почему это важно: При вставке объекта Python вычисляет его хеш и использует его для определения «корзины» (bucket). При последующем поиске по тому же объекту вычисляется новый хеш. Если он отличается, поиск будет вестись в другой корзине, и объект не будет найден, хотя физически он там есть.

Пример проблемного класса:

class MutableHash:
    def __init__(self, name):
        self.name = name  # Используем атрибут для хеширования

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        return isinstance(other, MutableHash) and self.name == other.name

obj = MutableHash('Alice')
my_set = {obj}
print(obj in my_set)  # True

obj.name = 'Bob'      # Меняем атрибут, от которого зависит хеш!
print(obj in my_set)  # False! Объект «потерялся»
print(hash(MutableHash('Alice'))) # Старый хеш
print(hash(obj))                  # Новый хеш. Они не совпадают.

Правила:

  • Неизменяемые встроенные типы (int, str, tuple) имеют постоянный хеш.
  • Изменяемые типы (list, dict, set) нехэшируемы (у них нет __hash__).
  • Пользовательские классы по умолчанию хэшируемы по id(), но если вы переопределяете __eq__, следует также переопределить __hash__, основывая его на неизменяемых атрибутах.