John Millikin zaproponował rozwiązanie podobne do tego:
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))
Problem z tym rozwiązaniem polega na tym, że plik hash(A(a, b, c)) == hash((a, b, c))
. Innymi słowy, hash koliduje z krotką jego kluczowych członków. Może w praktyce nie ma to zbyt często znaczenia?
Aktualizacja: dokumentacja Pythona zaleca teraz używanie krotki, jak w powyższym przykładzie. Zauważ, że w dokumentacji podano
Jedyną wymaganą właściwością jest to, że obiekty, które porównują równe wartości, mają tę samą wartość skrótu
Zauważ, że nie jest odwrotnie. Obiekty, które nie są ze sobą równe, mogą mieć tę samą wartość skrótu. Taka kolizja skrótów nie spowoduje, że jeden obiekt zastąpi inny, gdy zostanie użyty jako klucz dict lub element zestawu, o ile obiekty nie są również równe .
Nieaktualne / złe rozwiązanie
Dokumentacja Pythona__hash__
sugeruje połączenie skrótów podskładników za pomocą czegoś takiego jak XOR , co daje nam to:
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)))
Aktualizacja: jak wskazuje Blckknght, zmiana kolejności a, b i c może powodować problemy. Dodałem dodatkowy, ^ hash((self._a, self._b, self._c))
aby uchwycić kolejność haszowanych wartości. To końcowe ^ hash(...)
można usunąć, jeśli łączonych wartości nie można zmienić (na przykład, jeśli mają one różne typy i dlatego wartość _a
nigdy nie zostanie przypisana do _b
lub _c
itp.).
__key
funkcji, jest to tak szybkie, jak to tylko możliwe. Jasne, jeśli wiadomo, że atrybuty są liczbami całkowitymi, a nie ma ich zbyt wiele, przypuszczam, że można by potencjalnie działać nieco szybciej z jakimś hashem wyrzuconym na miejsce, ale prawdopodobnie nie byłby tak dobrze rozłożony.hash((self.attr_a, self.attr_b, self.attr_c))
będzie zaskakująco szybkie (i poprawne ), ponieważ tworzenie małychtuple
s jest specjalnie zoptymalizowane i popycha pracę nad pobieraniem i łączeniem skrótów do wbudowanych C, co jest zwykle szybsze niż kod na poziomie Pythona.