Подтвердить что ты не робот

Как реализовать хорошую функцию __hash__ в python

При реализации класса с несколькими свойствами (например, в примере с игрушкой ниже) лучший способ обработки хэширования?

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

class AClass:
  def __init__(self):
      self.a = None
      self.b = None

  def __eq__(self, other):
      return other and self.a == other.a and self.b == other.b

  def __ne__(self, other):
    return not self.__eq__(other)

  def __hash__(self):
      return hash((self.a, self.b))

Я читал на этом вопросе, что кортежи хешируются, поэтому мне было интересно, было ли что-то вроде приведенного выше примера разумным. Это?

4b9b3361

Ответ 1

__hash__ должно возвращать одно и то же значение для одинаковых объектов. Он также не должен меняться в течение всего жизненного цикла объекта; как правило, вы реализуете его только для неизменяемых объектов.

Тривиальная реализация будет состоять только в return 0. Это всегда правильно, но плохо работает.

Ваше решение, возвращая хэш кортежа свойств, является хорошим. Но учтите, что вам не нужно перечислять все свойства, которые вы сравниваете в __eq__ в кортеже. Если какое-то свойство обычно имеет одинаковое значение для неравных объектов, просто оставьте его. Не делайте вычисления хэша более дорогостоящими, чем это должно быть.

Изменить: я бы рекомендовал использовать xor для микширования хэшей вообще. Когда два разных свойства имеют одинаковое значение, они будут иметь один и тот же хеш, и с xor они отменяют друг друга. Кортежи используют более сложный расчет для смешивания хэшей, см. tuplehash в tupleobject.c.

Ответ 2

Опасно писать

def __eq__(self, other):
  return other and self.a == other.a and self.b == other.b

потому что если ваш объект rhs (т.е. other) оценивает значение boolean False, он никогда не сравнится ни с чем!

Кроме того, вы можете дважды проверить, принадлежит ли other классу или подклассу AClass. Если это не так, вы получите либо исключение AttributeError, либо ложное положительное (если другой класс имеет одинаковые атрибуты с соответствующими значениями). Поэтому я бы рекомендовал переписать __eq__ как:

def __eq__(self, other):
  return isinstance(other, self.__class__) and self.a == other.a and self.b == other.b

Если вы хотите необычно гибкое сравнение, которое сравнивается между несвязанными классами, пока атрибуты соответствуют по имени, вам все равно нужно, по крайней мере, избегать AttributeError и проверить, что other не имеет дополнительные атрибуты. Как вы это делаете, это зависит от ситуации (поскольку нет стандартного способа найти все атрибуты объекта).

Ответ 3

Документация для object.__hash__(self)

Единственное требуемое свойство состоит в том, что объекты, которые сравнивают одинаковые, имеют одно и то же значение хэш-функции; рекомендуется каким-то образом смешивать (например, с использованием эксклюзивных или) хеш-значений для компонентов объекта, которые также играют роль в сравнении объектов.

def __hash__(self):
    return hash(self.a) ^ hash(self.b)