似乎在阅读this answer时,如果在自定义类中定义__eq__
,则还需要定义__hash__
。这是可以理解的
但是,目前尚不清楚,为什么 - 有效 - __eq__
应与self.__hash__()==other.__hash__
想象一下这样的课:
class Foo:
...
self.Name
self.Value
...
def __eq__(self,other):
return self.Value==other.Value
...
def __hash__(self):
return id(self.Name)
这样,类实例可以通过值进行比较,这可能是唯一合理的用途,但在名称上被视为相同
这种方式set
不能包含具有相同名称的多个实例,但比较仍然有效。
这种定义可能会出现什么问题?
__eq__
,__lt__
和其他Value
定义的原因是能够按Value
对实例进行排序,并且能够使用max等函数。例如,他的类应该代表设备的物理输出(比如加热元件)。每个输出都有唯一的名称。值是输出设备的功率。为了找到加热元件的最佳组合以便打开,能够通过功率(值)进行比较是有用的。但是,在集合或字典中,不应该有多个具有相同名称的输出。当然,具有不同名称的不同输出可能很容易具有相同的功率。
答案 0 :(得分:6)
问题在于它没有意义,哈希用于对对象进行有效的分组。因此,当您有一个实现为哈希表的集合时,每个哈希都指向一个桶,它通常是一个元素列表。为了检查元素是否在集合(或其他基于散列的容器)中,您转到散列指向的存储桶,然后迭代列表中的所有元素,逐个进行比较。
换句话说 - 哈希不应该是比较器(因为它可以,并且应该给你有时误报)。特别是,在您的示例中,您的集合将无法工作 - 它将无法识别重复,因为它们不会相互比较。
class Foo:
def __eq__(self,other):
return self.Value==other.Value
def __hash__(self):
return id(self.Name)
a = set()
el = Foo()
el.Name = 'x'
el.Value = 1
el2 = Foo()
el2.Name = 'x'
el2.Value = 2
a.add(el)
a.add(el2)
print len(a) # should be 1, right? Well it is 2
实际上它更糟糕的是,如果你有两个具有相同值但名称不同的对象,它们不会被识别为相同
class Foo:
def __eq__(self,other):
return self.Value==other.Value
def __hash__(self):
return id(self.Name)
a = set()
el = Foo()
el.Name = 'x'
el.Value = 2
el2 = Foo()
el2.Name = 'a'
el2.Value = 2
a.add(el)
a.add(el2)
print len(a) # should be 1, right? Well it is 2 again
正确地做(因此,"如果a == b,那么哈希(a)==哈希(b)")给出:
class Foo:
def __eq__(self,other):
return self.Name==other.Name
def __hash__(self):
return id(self.Name)
a = set()
el = Foo()
el.Name = 'x'
el.Value = 1
el2 = Foo()
el2.Name = 'x'
el2.Value = 2
a.add(el)
a.add(el2)
print len(a) # is really 1
还有一个非确定性部分,很难轻易复制,但实质上哈希并不能唯一地定义一个存储桶。通常就像
bucket_id = hash(object) % size_of_allocated_memory
因此,具有不同散列的东西仍然可以在同一个桶中结束。因此,即使名称不同,您也可以获得两个等于每个元素(内部集合)的元素,即使名称不同,反之亦然,这取决于实际的内部实现,内存约束等。
一般来说,还有更多的例子会出现问题,因为哈希被定义作为函数h : X -> Z
,因此x == y => h(x) == h(y)
,因此人们实现了他们的容器,授权协议和其他工具可以假设这个属性。如果你打破它 - 使用哈希的每一个工具都可能破坏。此外,它可以突破及,这意味着您更新了一些库并且您的代码将停止工作,因为对底层库的有效更新(使用上述假设)可能会导致您违反此行为假设。
最后,为了解决您的问题 - 您不应该定义 eq , lt 运算符来处理排序。这是关于元素的实际比较,它应该与其他行为兼容。您所要做的就是定义一个单独的比较器并在您的排序例程中使用它(在python中排序接受任何比较器,您不需要依赖<,>等)。另一种方法是在值上定义有效的&lt ;,>,=,但是为了使名称保持唯一 - 保留一个带有... well ...名称的集合,而不是对象本身。无论你选择哪条路 - 这里的关键因素是: 平等和散列必须兼容,这就是全部。
答案 1 :(得分:-1)
可以像这样实现你的类,不有任何问题。但是,您必须100%确定没有两个不同的对象将生成相同的哈希。请考虑以下示例:
class Foo:
def __init__(self, name, value):
self.name= name
self.value= value
def __eq__(self, other):
return self.value == other.value
def __hash__(self):
return hash(self.name[0])
s= set()
s.add(Foo('a', 1))
s.add(Foo('b', 1))
print(len(s)) # output: 2
但是如果发生哈希冲突,你会遇到问题:
s.add(Foo('abc', 1))
print(len(s)) # output: 2
为了防止出现这种情况,您必须知道完全如何生成哈希值(如果您依赖id
或hash
等函数,则可能会有所不同实现之间!)以及用于生成散列的属性的值(在此示例中为name
)。这就是为什么排除哈希冲突的可能性是非常困难的,如果不是不可能的话。这基本上就像是在乞求意想不到的事情发生。