Miałem podklasyfikowany, aby dodać dodatkową metodę (tak bez nadrzędnej).

Teraz próbuję porównać dwie z tych podklas i dostaję coś dziwnego:

>>> d1.items() == d2.items()
True
>>> d1.values() == d2.values()
True
>>> d1.keys() == d2.keys()
True
>>> d1 == d2
False

EDYTUJ

To cholernie dziwne ... W ogóle nie rozumiem! Ktoś z wglądem, jak wdrażane jest dykt. eq ?

Poniżej znajduje się cały kod:

# ------ Bellow is my dict subclass (with no overriding) :

class ClassSetDict(dict):

    def subsetget(self, klass, default=None):
        class_sets = set(filter(lambda cs: klass <= cs, self))
        # Eliminate supersets
        for cs1 in class_sets.copy():
            for cs2 in class_sets.copy():
                if cs1 <= cs2 and not cs1 is cs2:
                    class_sets.discard(cs2)
        try:
            best_match = list(class_sets)[0]
        except IndexError:
            return default
        return self[best_match]

# ------  Then an implementation of class sets

class ClassSet(object):
    # Set of classes, allowing to easily calculate inclusions
    # with comparison operators : `a < B` <=> "A strictly included in B"

    def __init__(self, klass):
        self.klass = klass

    def __ne__(self, other):
        return not self == other

    def __gt__(self, other):
        other = self._default_to_singleton(other)
        return not self == other and other < self

    def __le__(self, other):
        return self < other or self == other

    def __ge__(self, other):
        return self > other or self == other

    def _default_to_singleton(self, klass):
        if not isinstance(klass, ClassSet):
            return Singleton(klass)
        else:
            return klass


class Singleton(ClassSet):

    def __eq__(self, other):
        other = self._default_to_singleton(other)
        return self.klass == other.klass

    def __lt__(self, other):
        if isinstance(other, AllSubSetsOf):
            return issubclass(self.klass, other.klass)
        else:
            return False


class AllSubSetsOf(ClassSet):

    def __eq__(self, other):
        if isinstance(other, AllSubSetsOf):
            return self.klass == other.klass
        else:
            return False

    def __lt__(self, other):
        if isinstance(other, AllSubSetsOf):
            return issubclass(self.klass, other.klass) and not other == self
        else:
            return False

# ------ and finally the 2 dicts that don't want to be equal !!!

d1 = ClassSetDict({AllSubSetsOf(object): (int,)})
d2 = ClassSetDict({AllSubSetsOf(object): (int,)})
4
sebpiq 7 grudzień 2011, 16:49

4 odpowiedzi

Najlepsza odpowiedź

Problem, który nie ma nic wspólnego z podklasami {x0}}. W rzeczywistości to zachowanie można zobaczyć za pomocą regularnego dyktatu. Problem polega na tym, jak zdefiniowałeś klawisze używasz. Prosta klasa jak:

>>> class Foo(object):
...     def __init__(self, value):
...         self.value = value
... 
...     def __eq__(self, other):
...         return self.value == other.value
... 

Wystarczy, aby zademonstrować problem:

>>> f1 = Foo(5)
>>> f2 = Foo(5)
>>> f1 == f2
True
>>> d1 = {f1: 6}
>>> d2 = {f2: 6}
>>> d1.items() == d2.items()
True
>>> d1 == d2
False

Brakuje, że zapomniałeś zdefiniować __hash__. Za każdym razem, gdy zmienisz semantykę równości klasy, powinieneś upewnić się, że metoda __hash__ zgadza się z nim: gdy dwa obiekty są równe, muszą mieć równe skróty. dict Zachowanie zależy zależy silnie silnie na wartości kluczy.

Kiedy dziedzicisz z object, automatycznie otrzymasz zarówno __eq__, jak i __hash__, pierwsza porównuje tożsamość obiektu, a druga zwraca adres obiektu (więc zgadzają się), ale kiedy ty Zmień __eq__, nadal widzisz stare __hash__, który już nie zgadza się, a dict zgubiona.

Wystarczy podać metodę __hash__, że w stabilnym sposobie łączy wartości Hash jego atrybutów.

>>> class Bar(object):
...     def __init__(self, value):
...         self.value = value
... 
...     def __eq__(self, other):
...         return self.value == other.value
... 
...     def __hash__(self):
...         return hash((Bar, self.value))
... 
>>> b1 = Bar(5)
>>> b2 = Bar(5)
>>> {b1: 6} == {b2: 6}
True
>>> 

Podczas korzystania z __hash__ jest to również dobry pomysł, aby upewnić się, że atrybuty nie (lub lepiej, nie mogą) zmienić po utworzeniu obiektu. Jeśli zmienia się wartość Hash, przy czym zebrano w dykt, klucz zostanie "zagubiony", a wszelkiego rodzaju dziwne rzeczy mogą się zdarzyć (nawet dziwniejsze niż kwestia, o której pytasz)

9
SingleNegationElimination 7 grudzień 2011, 15:34

To prawdopodobnie zależy od pewnych szczegółów wdrożenia, w rzeczywistości podstawowe podklasy nie pokazuje tego problemu:

>>> class D(dict):
...    def my_method(self):
...        pass
... 
>>> d1 = D(alpha=123)
>>> d1
{'alpha': 123}
>>> d2 = D(alpha=123)
>>> d1.items() == d2.items()
True
>>> d1.values() == d2.values()
True
>>> d1.keys() == d2.keys()
True
>>> d1 == d2
True
3
mac 7 grudzień 2011, 13:25

Twoja instancja "ALLSUSETSOF" ASRE używana jako dawkowe klucze - powinny mieć metodę Hash . Spróbuj dodać A.

def __hash__(self): 
   return hash(self.klass)

Metoda do zestawu klasowego lub allsubsetsof

1
jsbueno 7 grudzień 2011, 13:19

Nienawidzę tego, gdy ludzie mówią, co takie jak "Dyktury zawierają funky rzeczy, więc nie pomogło mu było zobaczyć", ponieważ jest to właśnie charakter funkych rzeczy, które się tu liczy.

Pierwszą rzeczą do zanotowania jest to, że gdybyś miał dokładnie odwrotny wynik, nie byłoby zaskakujące: tj To dlatego, że słowniki nie porównują, porównując elementy lub klucze, używają innej techniki, która (myślę) jest źródłem twojego problemu.

Skutecznie porównywanie dwóch słowników najpierw sprawdza, a następnie przechodzi przez wszystkie klucze w pierwszym słowniku, aby znaleźć najmniejszy, który nie pasuje do klucza / wartości z drugiego słownika. Więc właśnie szukamy, jest przypadkiem, w którym d1.keys()==d2.keys() ale dla niektórych k, albo k not in d1 or k not in d2 or d1[k] != d2[k].

Myślę, że wskazówka może być w obiektach, których używasz jako klucze słownikowe. Jeśli są go uznawane, możesz przechowywać obiekt w słowniku, ale następnie mutację go i staje się niedostępny dzięki normalnym środkiem. Metoda keys() może nadal go znaleźć iw takim przypadku mógłbyś dostać to, co widzisz.

Teraz zaktualizowałeś pytanie za pomocą klasy AllSubSetsOf: jest to brak metody {X1}}, która jest problemem. Dwie różne instancje mogą porównać równe: AllSubSetsOf(object)==allSubSetsOf(object), ale wartości Hash są po prostu mieszając na adres, aby były różne.

>>> class AllSubSetsOf(object):
    def __init__(self, klass):
        self.klass = klass

    def __eq__(self, other):
        if isinstance(other, AllSubSetsOf):
            return self.klass == other.klass
        else:
            return False

    def __lt__(self, other):
        if isinstance(other, AllSubSetsOf):
            return issubclass(self.klass, other.klass) and not other == self
        else:
            return False


>>> a = AllSubSetsOf(object)
>>> b = AllSubSetsOf(object)
>>> a==b
True
>>> hash(a), hash(b)
(2400161, 2401895)
>>> 
1
Duncan 7 grudzień 2011, 13:27