比较dict子类的实例

时间:2011-12-07 12:49:18

标签: python

我已经将dict子类添加了一个额外的方法(所以没有覆盖)。

现在,我尝试比较其中两个子类,我得到一些奇怪的东西:

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

修改

那真是太奇怪了......我根本不明白!是否有人了解dict。 eq 是如何实现的?

以下是所有代码:

# ------ 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 个答案:

答案 0 :(得分:8)

你所看到的问题与子类化dict没有任何关系。事实上,使用常规字典可以看到这种行为。问题是您如何定义正在使用的。一个简单的类,如:

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

足以证明问题:

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

缺少的是您忘记定义__hash__。每次更改类的相等语义时,都应确保__hash__方法同意它:当两个对象相等时,它们必须具有相等的哈希值。 dict行为取决于强烈对键的哈希值。

当你从object继承时,你自动获得__eq____hash__,前者比较对象身份,后者返回对象的地址(所以他们同意),但是当您更改__eq__时,您仍然会看到旧的__hash__,它已不再同意并且dict会丢失。

只需提供一个__hash__方法,以稳定的方式组合其属性的哈希值。

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

以这种方式使用__hash__时,确保在创建对象后属性不会(或更好,不能)更改也是一个好主意。如果哈希值在dict中收集时发生变化,则密钥将“丢失”,并且可能发生各种奇怪的事情(甚至比您最初询问的问题更奇怪)

答案 1 :(得分:3)

这很可能取决于一些实现细节,实际上基本的子类化并没有显示这个问题:

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

答案 2 :(得分:1)

您的“所有子集”实例用作dict键 - 它们应该具有哈希方法。 尝试添加

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

ClassSet或AllSubSetsOf的方法

答案 3 :(得分:1)

当人们说“这些词语包含时髦的东西,因此它显示出来时没什么用”时,我真的很讨厌它,因为它恰恰是这里重要的时髦东西的本质。

首先要注意的是,如果你得到了完全相反的结果,那就完全没有意外了:即如果d1.items(), d1.values(), d1.keys()不等于d2.items(), d2.values(), d2.keys()你就可以很高兴{{1} }}。那是因为字典不能通过比较项目或键来比较,它们使用不同的技术(我认为)是你问题的根源。

有效地比较两个字典首先检查它们是否具有相同的长度,然后遍历第一个字典中的所有键以找到与第二个字典中的键/值不匹配的最小字典。所以我们实际需要的是d1 == d2,但对于某些k d1.keys()==d2.keys()

我认为线索可能在您用作字典键的对象中。如果它们是可变的,你可以将一个对象存储在字典中,但随后将其变异,并且通过常规手段变得无法访问。 k not in d1 or k not in d2 or d1[k] != d2[k]方法可能仍会找到它,在这种情况下,您可以得到您所看到的内容。

现在您已使用keys()类更新了问题:问题是缺少AllSubSetsOf方法。两个不同的实例可以比较相等:__hash__()但哈希值只是对地址进行散列,因此它们会有所不同。

AllSubSetsOf(object)==allSubSetsOf(object)