John Millikin propôs uma solução semelhante 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))
O problema com esta solução é que o hash(A(a, b, c)) == hash((a, b, c))
. Em outras palavras, o hash colide com o da tupla de seus principais membros. Talvez isso não importe com muita frequência na prática?
Atualização: os documentos do Python agora recomendam o uso de uma tupla, como no exemplo acima. Observe que a documentação declara
A única propriedade necessária é que os objetos que comparam iguais tenham o mesmo valor de hash
Note que o oposto não é verdadeiro. Objetos que não comparam iguais podem ter o mesmo valor de hash. Essa colisão de hash não fará com que um objeto substitua outro quando usado como uma chave de ditado ou elemento de conjunto , desde que os objetos também não sejam comparáveis .
Solução desatualizada / incorreta
A documentação do Python__hash__
sugere que você combine os hashes dos subcomponentes usando algo como o XOR , o que nos dá o seguinte:
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)))
Atualização: como Blckknght aponta, alterar a ordem de a, bec pode causar problemas. Eu adicionei um adicional ^ hash((self._a, self._b, self._c))
para capturar a ordem dos valores que estão sendo hash. Esta final ^ hash(...)
pode ser removida se os valores combinados não puderem ser reorganizados (por exemplo, se eles tiverem tipos diferentes e, portanto, o valor de _a
nunca será atribuído a _b
ou _c
, etc.).
__key
função, isso é tão rápido quanto qualquer hash pode ser. Claro, se se sabe que os atributos são números inteiros e não há muitos, suponho que você possa executar um pouco mais rápido com um hash de rolagem em casa, mas provavelmente não seria tão bem distribuído.hash((self.attr_a, self.attr_b, self.attr_c))
será surpreendentemente rápido (e correto ), pois a criação de pequenostuple
s é especialmente otimizada, e empurra o trabalho de obter e combinar hashes para os C embutidos, o que normalmente é mais rápido que o código no nível Python.