Как реализовать хорошую хэш-функцию в 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))

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

3 77

3 ответа:

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

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

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

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

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

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

потому что если ваш rhs (т. е.,other) объект вычисляет логическое значение 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 не имеет никаких дополнительных атрибутов. Как это сделать, зависит от ситуации (так как нет стандартного способа найти все атрибуты объекта).

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

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