John Millikin propuso una solución similar a esta:
class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return (isinstance(othr, type(self))
and (self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return hash((self._a, self._b, self._c))
El problema con esta solución es que el hash(A(a, b, c)) == hash((a, b, c))
. En otras palabras, el hash choca con el de la tupla de sus miembros clave. Tal vez esto no importa muy a menudo en la práctica?
Actualización: los documentos de Python ahora recomiendan usar una tupla como en el ejemplo anterior. Tenga en cuenta que la documentación indica
La única propiedad requerida es que los objetos que comparan igual tienen el mismo valor hash
Tenga en cuenta que lo contrario no es cierto. Los objetos que no se comparan igual pueden tener el mismo valor hash. Tal colisión de hash no hará que un objeto reemplace a otro cuando se use como una clave dict o elemento de ajuste , siempre que los objetos no se comparen igual .
Solución desactualizada / mala
La documentación de Python__hash__
sugiere combinar los hashes de los subcomponentes usando algo como XOR , lo que nos da esto:
class B(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
if isinstance(othr, type(self)):
return ((self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
return NotImplemented
def __hash__(self):
return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
hash((self._a, self._b, self._c)))
Actualización: como señala Blckknght, cambiar el orden de a, byc podría causar problemas. Agregué un adicional ^ hash((self._a, self._b, self._c))
para capturar el orden de los valores que se están dividiendo. Este final ^ hash(...)
se puede eliminar si los valores que se combinan no se pueden reorganizar (por ejemplo, si tienen diferentes tipos y, por lo tanto, el valor de _a
nunca se asignará a _b
o _c
, etc.).
__key
función, esto es casi tan rápido como cualquier hash puede ser. Claro, si se sabe que los atributos son enteros, y no hay demasiados, supongo que potencialmente podría correr un poco más rápido con un poco de hash casero, pero probablemente no estaría tan bien distribuido.hash((self.attr_a, self.attr_b, self.attr_c))
va a ser sorprendentemente rápido (y correcto ), ya que la creación de pequeñostuple
s está especialmente optimizada, y empuja el trabajo de obtener y combinar hashes en C incorporados, que generalmente es más rápido que el código de nivel de Python.