Frage Wie man eine gute __hash__ Funktion in Python implementiert


Wenn Sie eine Klasse mit mehreren Eigenschaften implementieren (wie im folgenden Beispiel), was ist der beste Weg, mit Hashing umzugehen?

Ich denke, dass das __eq__ und __hash__ sollte konsistent sein, aber wie eine richtige Hash-Funktion zu implementieren, die in der Lage ist, alle Eigenschaften zu behandeln?

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

Ich lese weiter diese Frage dass Tupel hashbar sind, also habe ich mich gefragt, ob etwas wie das obige Beispiel sinnvoll ist. Ist es?


76
2017-10-23 17:54


Ursprung


Antworten:


__hash__ sollte denselben Wert für Objekte, die gleich sind, zurückgeben. Es sollte auch nicht über die Lebensdauer des Objekts ändern; Im Allgemeinen implementieren Sie es nur für unveränderliche Objekte.

Eine triviale Implementierung wäre zu einfach return 0. Das ist immer richtig, aber funktioniert schlecht.

Ihre Lösung, die den Hash eines Tupels von Eigenschaften zurückgibt, ist gut. Beachten Sie jedoch, dass Sie nicht alle Eigenschaften auflisten müssen, mit denen Sie vergleichen __eq__ im Tupel. Wenn eine Eigenschaft für ungleiche Objekte normalerweise den gleichen Wert hat, lassen Sie sie einfach weg. Machen Sie die Hash-Berechnung nicht teurer als nötig.

Edit: Ich würde empfehlen, XOR zu verwenden, um Hashes im Allgemeinen zu mischen. Wenn zwei verschiedene Eigenschaften den gleichen Wert haben, haben sie den gleichen Hash, und mit xoder heben sie sich gegenseitig auf. Tupel verwenden eine komplexere Berechnung, um Hashes zu mischen, zu sehen tuplehash im tupleobject.c.


56
2017-10-23 18:19



Es ist gefährlich zu schreiben

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

denn wenn deine rhs (d. h. other) object evaluate to boolean False, es wird niemals als gleichwertig mit irgendetwas verglichen!

Darüber hinaus möchten Sie vielleicht überprüfen, ob other gehört zu der Klasse oder Unterklasse von AClass. Wenn dies nicht der Fall ist, erhalten Sie entweder eine Ausnahme AttributeError oder ein falsches positives Ergebnis (wenn die andere Klasse zufällig dieselben Attribute mit übereinstimmenden Werten hat). Also würde ich empfehlen, neu zu schreiben __eq__ wie:

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

Wenn Sie auf jeden Fall einen ungewöhnlich flexiblen Vergleich wünschen, der sich über nicht verwandte Klassen hinweg vergleichen lässt, solange die Attribute nach dem Namen übereinstimmen, möchten Sie dies zumindest vermeiden AttributeError und überprüfe das other hat keine zusätzlichen Attribute. Wie Sie es tun, hängt von der Situation ab (da es keine Standardmethode gibt, alle Attribute eines Objekts zu finden).


12
2017-09-20 11:31



Dokumentation für object.__hash__(self)

Die einzige erforderliche Eigenschaft besteht darin, dass Objekte, die gleich sind, denselben Hash-Wert haben; Es wird empfohlen, die Hash-Werte für die Komponenten des Objekts, die beim Vergleich von Objekten ebenfalls eine Rolle spielen, irgendwie miteinander zu vermischen (z. B. unter Verwendung von Exklusiv-Oder).

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

9
2017-10-23 18:11