Wie prüft ein Python-Set ([]), ob zwei Objekte gleich sind? Welche Methoden muss ein Objekt definieren, um dies anzupassen?

83

Ich muss in Python ein 'Container'-Objekt oder eine' Container'-Klasse erstellen, die andere Objekte aufzeichnet, die ich ebenfalls definiere. Eine Anforderung dieses Containers besteht darin, dass, wenn zwei Objekte als identisch angesehen werden, eines (eines davon) entfernt wird. Mein erster Gedanke war, a set([])als das enthaltende Objekt zu verwenden, um diese Anforderung zu erfüllen.

Die Menge entfernt jedoch keine der beiden identischen Objektinstanzen. Was muss ich definieren, um eine zu erstellen?

Hier ist der Python-Code.

class Item(object):
  def __init__(self, foo, bar):
    self.foo = foo
    self.bar = bar
  def __repr__(self):
    return "Item(%s, %s)" % (self.foo, self.bar)
  def __eq__(self, other):
    if isinstance(other, Item):
      return ((self.foo == other.foo) and (self.bar == other.bar))
    else:
      return False
  def __ne__(self, other):
    return (not self.__eq__(other))

Dolmetscher

>>> set([Item(1,2), Item(1,2)])
set([Item(1, 2), Item(1, 2)])

Es ist klar, dass das __eq__(), was von aufgerufen wird x == y, nicht die von der Menge aufgerufene Methode ist. Wie heißt das? Welche andere Methode muss ich definieren?

Hinweis: Das Items muss veränderbar bleiben und kann sich ändern, daher kann ich keine Methode bereitstellen __hash__(). Wenn dies der einzige Weg ist, werde ich für die Verwendung von unveränderlichen Items umschreiben .

Ada
quelle
1
Hatte das gleiche Problem. Ich gehe davon aus, dass Sie kleine Datenmengen in Ihrem Code manipulieren. Dies ist wahrscheinlich kein guter Kandidat für die Verwendung einer Datenbank. Ich erinnere mich, dass ich in C ++ eine Menge erstellen und eine Komparatorfunktion definieren konnte, und ich glaube auch, dass Java dies nicht mit Wörterbuchobjekten in Python tun kann. Es scheint, als hätte jemand eine "Set" -Bibliothek in Python geschrieben, die dies kann, aber mir ist keine bekannt.
Chris Dutrow

Antworten:

32

Ich fürchte, Sie müssen eine __hash__()Methode angeben. Aber Sie können es so codieren, dass es nicht von den veränderlichen Attributen Ihres abhängt Item.

Eumiro
quelle
3
< docs.python.org/reference/… > Im zweiten Absatz wird darauf hingewiesen, dass dies __hash__()nur für unveränderliche Objekte definiert werden sollte.
Ada
1
@Nathanael: Wenn sich das Objekt möglicherweise ändern muss, können Sie eine unveränderliche Kopie des Objekts erstellen, z. B. frozenset () und set ().
Lie Ryan
2
@Nathanael - wie möchten Sie anrufen __eq__? Vergleichen Sie diese (1,2) Attribute? Dann müssen Sie auch in Ihrer __hash__Methode einen Hash von (1,2) zurückgeben .
Eumiro
Oder könnte, da foo und bar beide unveränderlich sind, __hash __ () die Summe der Hashes von foo und bar zurückgeben? Nein ... Summe ist zu unzuverlässig ... wenn foo 1 und Takt 2 wäre, wäre das gleich bar als 1 mit foo als 2, was falsch ist. Welche mathematische Funktion kann für diesen Zweck verwendet werden? Modulo oder Division sollte funktionieren.
Ada
1
Nathanael: Verwenden hashSie einfach das eingebaute : hash((self.foo, self.bar)). Hierbei wird der Tupel-Hash verwendet, der Ihren Anforderungen entspricht. (Sie __eq__könnten auch zum tupleVergleich geschrieben werden.)
Pi Delport
73

Ja, Sie benötigen eine __hash__()Methode UND den Vergleichsoperator, den Sie bereits angegeben haben.

class Item(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
    def __repr__(self):
        return "Item(%s, %s)" % (self.foo, self.bar)
    def __eq__(self, other):
        if isinstance(other, Item):
            return ((self.foo == other.foo) and (self.bar == other.bar))
        else:
            return False
    def __ne__(self, other):
        return (not self.__eq__(other))
    def __hash__(self):
        return hash(self.__repr__())
Ruena
quelle
3
Auf Python 3 brauchen Sie nicht __ne__, und selbst in 2.x sollten Sie nicht __ne__in Bezug auf definieren __eq__. Siehe stackoverflow.com/a/30676267/5337834
John Strood