Pregunta Cómo implementar una buena función __hash__ en python


Al implementar una clase con propiedades múltiples (como en el ejemplo de juguete a continuación), ¿cuál es la mejor manera de manejar hash?

Supongo que el __eq__ y __hash__ debe ser consistente, pero ¿cómo implementar una función hash adecuada que sea capaz de manejar todas las propiedades?

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))

Leo en esta pregunta que las tuplas son lavables, así que me preguntaba si algo como el ejemplo anterior era sensato. ¿Lo es?


76
2017-10-23 17:54


origen


Respuestas:


__hash__ debería devolver el mismo valor para los objetos que son iguales. Tampoco debería cambiar a lo largo de la vida del objeto; en general, solo lo implementas para objetos inmutables.

Una implementación trivial sería simplemente return 0. Esto siempre es correcto, pero funciona mal.

Su solución, devolver el hash de una tupla de propiedades, es buena. Pero tenga en cuenta que no necesita enumerar todas las propiedades que compara en __eq__ en la tupla Si alguna propiedad generalmente tiene el mismo valor para los objetos desiguales, simplemente omítala. No haga que el cálculo de hash sea más caro de lo que debe ser.

Editar: recomendaría no usar xor para mezclar hashes en general. Cuando dos propiedades diferentes tienen el mismo valor, tendrán el mismo hash, y con xor éstas se cancelarán mutuamente. Las tuplas usan un cálculo más complejo para mezclar hashes, ver tuplehash en tupleobject.c.


56
2017-10-23 18:19



Es peligroso escribir

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

porque si tu rhs (es decir, other) el objeto evalúa a boolean False, ¡nunca se comparará con nada!

Además, es posible que desee comprobar si other pertenece a la clase o subclase de AClass. Si no lo hace, obtendrás una excepción AttributeError o un falso positivo (si la otra clase tiene los mismos atributos con valores coincidentes). Así que recomendaría reescribir __eq__ como:

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

Si, por casualidad, desea una comparación inusualmente flexible, que se compare entre clases no relacionadas, siempre y cuando los atributos coincidan por su nombre, aún desea evitar al menos AttributeError y comprueba que other no tiene ningún atributo adicional. Cómo lo hace depende de la situación (ya que no hay una forma estándar de encontrar todos los atributos de un objeto).


12
2017-09-20 11:31



Documentación para object.__hash__(self)

La única propiedad requerida es que los objetos que se comparan por igual tengan el mismo valor hash; se recomienda mezclar de alguna manera (por ejemplo, utilizando exclusivo o) los valores hash para los componentes del objeto que también desempeñan un papel en la comparación de los objetos.

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

9
2017-10-23 18:11