Как реализовать хорошую функцию __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))
Я читал на этом вопросе, что кортежи хешируются, поэтому мне было интересно, было ли что-то вроде приведенного выше примера разумным. Это?
Ответы
Ответ 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)