Luk*_*kas 4 python hash class equivalence
阅读这个答案看来,如果__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)
Run Code Online (Sandbox Code Playgroud)
这样,类实例可以通过值进行比较,这可能是唯一合理的用途,但是按名称认为是相同的.
这种方式set
不能包含具有相同名称的多个实例,但比较仍然有效.
这样的定义会出现什么问题?
之所以定义__eq__
,__lt__
以及其他Value
的能够通过对实例进行排序Value
,并能够使用功能,如最大值.例如,他的类应该代表设备的物理输出(比如加热元件).每个输出都有唯一的名称.值是输出设备的功率.为了找到加热元件的最佳组合以便打开,能够通过功率(值)进行比较是有用的.但是,在集合或字典中,不应该有多个具有相同名称的输出.当然,具有不同名称的不同输出可能容易具有相同的功率.
问题在于它没有意义,哈希用于对对象进行有效的分组.因此,当您有一个实现为哈希表的集合时,每个哈希都指向一个桶,它通常是一个元素列表.为了检查元素是否在集合(或其他基于散列的容器)中,您转到散列指向的存储桶,然后迭代列表中的所有元素,逐个进行比较.
换句话说 - 哈希不应该是比较器(因为它可以,并且应该给你有时误报).特别是,在您的示例中,您的集合将无法工作 - 它不会识别重复,因为它们不会相互比较.
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
Run Code Online (Sandbox Code Playgroud)
实际上更糟糕的是,如果你有两个具有相同值但名称不同的对象,它们不会被认为是相同的
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
Run Code Online (Sandbox Code Playgroud)
正确地做到这一点(因此,"如果a == b,那么hash(a)== hash(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
Run Code Online (Sandbox Code Playgroud)
还有一个非确定性部分,很难轻易复制,但实质上哈希并不能唯一地定义存储桶.通常它就像
bucket_id = hash(object) % size_of_allocated_memory
Run Code Online (Sandbox Code Playgroud)
因此,具有不同散列的东西仍然可以在同一个桶中结束.因此,由于值的相等性,即使名称不同,也可以获得两个等于每个元素(内部集合)的元素,以及相反的方式,具体取决于实际的内部实现,内存约束等.
通常,有许多例子在那里事情都可能出错,因为散列定义为一个函数h : X -> Z
,从而x == y => h(x) == h(y)
,从而实现人的容器,授权协议和其他工具都是免费的,以承担该属性.如果你打破它 - 使用哈希的每一个工具都可能破坏.此外,它可以打破时间,这意味着你更新一些库,你的代码将停止工作,作为一个有效的更新底层库(使用上述的前提下),可能会导致利用你违反这个假设.
最后,为了解决您的问题 - 您根本不应该定义您的eq,lt运算符来处理排序.它是关于元素的实际比较,它应该与其他行为兼容.您所要做的就是定义一个单独的比较器并在您的排序例程中使用它(在python中排序接受任何比较器,您不需要依赖<,>等).另一种方法是在值上定义有效的<,>,=,但为了保持名称的唯一性 - 保留一个带有...... well ...名称的集合,而不是对象本身.无论你选择哪条路径 - 这里的关键因素是: 平等和散列必须兼容,就是这样.
归档时间: |
|
查看次数: |
265 次 |
最近记录: |