我已经将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,)})
答案 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)