W jaki sposób Python ustawia ([]) sprawdza, czy dwa obiekty są równe? Jakie metody musi zdefiniować obiekt, aby to dostosować?

85

Potrzebuję utworzyć obiekt lub klasę „kontenera” w Pythonie, która przechowuje zapis innych obiektów, które również definiuję. Jednym z wymagań tego kontenera jest to, że jeśli dwa obiekty zostaną uznane za identyczne, jeden (albo jeden) zostanie usunięty. Moją pierwszą myślą było użycie a set([])jako obiektu zawierającego, aby spełnić to wymaganie.

Jednak zestaw nie usuwa jednej z dwóch identycznych instancji obiektu. Co muszę zdefiniować, aby go utworzyć?

Oto kod w Pythonie.

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

Interpretator

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

Oczywiste jest, że to __eq__(), co jest wywoływane przez x == y, nie jest metodą wywoływaną przez zestaw. Jak się nazywa? Jaką inną metodę muszę zdefiniować?

Uwaga: Items muszą pozostać zmienne i mogą się zmieniać, więc nie mogę podać __hash__()metody. Jeśli to jedyny sposób, aby to zrobić, przepiszę tak, aby używał niezmiennych Items.

Ada
źródło
1
Miałem ten sam problem. Zakładam, że manipulujesz niewielkimi ilościami danych w swoim kodzie. Prawdopodobnie nie jest to dobry kandydat do korzystania z bazy danych. Pamiętam, że mogłem stworzyć zestaw i zdefiniować funkcję komparatora w C ++ i uważam, że Java również, jednak nie wygląda na to, że można to zrobić z obiektami słownika w Pythonie. Wygląda na to, że ktoś mógł napisać bibliotekę „zestawu” w Pythonie, która może to zrobić, ale ja o niej nie wiem.
Chris Dutrow

Odpowiedzi:

32

Obawiam się, że będziesz musiał podać __hash__()metodę. Ale możesz to zakodować w taki sposób, że nie zależy to od zmiennych atrybutów twojego Item.

eumiro
źródło
3
< docs.python.org/reference/… > W drugim akapicie wskazano, że __hash__()powinno to być zdefiniowane tylko dla niezmiennych obiektów.
Ada
1
@Nathanael: jeśli obiekt może wymagać zmiany, możesz utworzyć niezmienną kopię obiektu, na przykład frozenset () i set ().
Lie Ryan
2
@Nathanael - jak chcesz zadzwonić __eq__? Porównanie tych (1, 2) atrybutów? Następnie musisz zwrócić trochę skrótu (1,2) również w swojej __hash__metodzie.
eumiro
A może, skoro oba foo i bar są niezmienne, __hash __ () może zwrócić sumę skrótów foo i bar? Nie ... suma jest zbyt niewiarygodna ... gdyby foo wynosiło 1, a takt 2, to byłoby równe taktowi jako 1 z foo równym 2, co jest błędne. Jakiej funkcji matematycznej można użyć do tego celu? Modulo lub dzielenie powinny działać.
Ada
1
Natanael: Wystarczy użyć hashpolecenie wbudowane: hash((self.foo, self.bar)). To używa skrótu krotki, który jest odpowiedni dla Twoich potrzeb. (Twój __eq__mógłby być również zapisany w kategoriach tupleporównania.)
Pi Delport
76

Tak, potrzebujesz __hash__()-method ORAZ operatora porównania, który już podałeś.

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
źródło
3
W Pythonie 3 nie potrzebujesz __ne__, a nawet w 2.x nie powinieneś definiować __ne__w kategoriach __eq__. Zobacz, stackoverflow.com/a/30676267/5337834
John Strood