2011-12-07 26 views
3

我有子类dict添加一个额外的方法(所以没有重写)。比较一个字典子类的实例

现在,我尝试比较两个那些子类,我也得到一些奇怪:

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

编辑

那该死的怪异......我不明白了!任何人都能洞悉这个词典。 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,)}) 
+2

这将有助于看你怎么划分子类'dict' ...向我们展示代码! :) – mac

+0

是同一类型的'd1'和'd2'? – phimuemue

+0

是的同类型... @mac:Yeaaah :)我知道......但它是如此时髦,我想它的,如果有人知道这是一个普遍的问题好多了!但是好的...我会显示... – sebpiq

回答

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__,它也是一个不错的主意,以确保所创建的对象后,属性不(或更好,也不能)的变化。如果散列值的变化,而在一个字典收集,钥匙就会被“丢失”,以及各种奇怪的事情可以发生(超过您最初询问此事甚至怪异)

+0

干得好......当我幸运地看到你的帖子并为自己挽救了自己的耻辱时,我正在研究一个完全不同的思路(排序的集合)! ;)+1 – mac

+0

太棒了!有用 !还有一个问题,你认为''散列(self .__ class __.__ name__)+ hash(self.klass)''对于ClassSet'来说是一个很好的散列吗?我特别想知道哈希(self.klass)哈希类。我记得我有一个奇怪的行为,这取决于你如何导入类:-S – sebpiq

+0

使用'self .__ class__'可能是一个糟糕的主意,如果你可能使用一个子类;哈希值将会改变(因为类型已经改变),但平等不会,所以你回到原点。实际上拼出课程是避免问题的好方法。另一种解决方法是仅从哈希计算中抽取类,但当属性(在我的示例中为“值”)本身位于集合中时,这可能会导致不希望的冲突。 – SingleNegationElimination

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 
1

你AllSubSetsOf的”实例“asre用作字典键 - 他们应该有一个散列方法。 尝试添加

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

方法要么ClassSet或AllSubSetsOf

1

我真恨它,当人们说这样的话“的类型的字典包含时髦的东西,所以它不会帮助不大,以示”,因为它正是这里重要的时髦东西的本质。

要注意的第一件事是,如果你有完全相反的结果,它不会是不足为奇的:即如果d1.items(), d1.values(), d1.keys()不等于d2.items(), d2.values(), d2.keys()你可以很愉快有d1 == d2。这是因为词典不能通过比较项目或键来进行比较,他们使用不同的技术(我认为)是问题的根源。

有效比较两个词典首先检查它们的长度是否相同,然后遍历第一个词典中的所有键以找到与第二个词典中键/值不匹配的最小键。所以我们实际上在寻找的是d1.keys()==d2.keys()但是对于某些k k not in d1 or k not in d2 or d1[k] != d2[k]的情况。

我认为线索可能在您用作字典键的对象中。如果它们是可变的,你可以在字典中存储一个对象,但然后改变它,并通过常规手段变得不可访问。 keys()方法仍然可以找到它,在这种情况下,你可以得到你所看到的。

现在你已经用AllSubSetsOf这个类更新了这个问题:它是缺少的__hash__()方法是问题所在。两个不同的实例可以比较等于:AllSubSetsOf(object)==allSubSetsOf(object),但散列值只是散列在地址上,所以它们会不同。

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