我来自Java,即使可变对象也可以“哈希”。
这些天,我在玩Python 3.x只是为了好玩。
这是Python中的hashable定义(来自Python词汇表)。
可哈希
如果对象的哈希值在其生命周期内始终不变(需要使用
__hash__()
方法,并且可以与其他对象进行比较(需要使用__eq__()
方法),则该对象是可哈希的。比较相等的可哈希对象必须具有相同的哈希值。可散列性使对象可用作字典键和set成员,因为这些数据结构在内部使用散列值。
所有Python不变的内置对象都是可哈希的;可变容器(例如列表或字典)不是。默认情况下,作为用户定义类实例的对象是可哈希的。它们都比较不相等(除了它们本身),并且其哈希值是从其
id()
派生的。
我读了,我在想... 仍然...为什么它们在Python中甚至不使可变对象可哈希化?例如。使用与用户定义的对象相同的默认哈希机制,即上述最后两句话所描述的。
作为用户定义类实例的对象默认情况下是可哈希的。它们都比较不相等(除了它们自己),并且其哈希值是从其id()派生的。
这感觉有点奇怪...因此,用户定义的可变对象是可哈希的(通过此默认哈希机制),但是内置的可变对象是不可哈希的。这不只是使事情复杂化了吗?我看不出它会带来什么好处,有人可以解释吗?
答案 0 :(得分:2)
在Python中,可变对象 可以是可散列的,但这通常不是一个好主意,因为通常来讲, quality 是根据这些可变属性定义的,这会导致各种疯狂的行为。
如果内置的可变对象是根据身份进行哈希处理的,例如用户定义对象的默认哈希处理机制,则它们的哈希将与它们的相等性不一致。这绝对是一个问题。但是,默认情况下,用户定义的对象会根据身份进行比较和散列,因此,虽然这组事务不是很有用,但情况并非如此。
请注意,如果您在用户定义的类中实现__eq__
,则__hash__
会设置为None
,从而使类不可散列。
So, from the Python 3 data model documentation:
用户定义的类具有
__eq__()
和__hash__()
方法 默认;与他们,所有对象比较不平等(与 本身)和x.__hash__()
返回适当的值,使得x == y
表示x is y
和hash(x) == hash(y)
。覆盖
__eq__()
且未定义__hash__()
的类将其__hash__()
隐式设置为None
。当。。。的时候 类的__hash__()
方法是None
,当程序尝试检索时,该类的实例将引发适当的TypeError。 它们的哈希值,也将正确地标识为不可哈希 在检查isinstance(obj, collections.abc.Hashable)
时。
答案 1 :(得分:1)
计算哈希值就像为对象赋予一个标识,从而简化了对象的比较。按哈希值进行比较通常比按值进行比较要快:对于一个对象,您可以比较其属性,对于一个集合,则可以递归地比较其项...
如果对象是可变的,则每次更改后都需要再次计算其哈希值。如果将此对象与另一个对象进行比较,则更改后它将变得不相等。因此,可变对象必须按值而不是哈希进行比较。按哈希值比较可变对象是一种非发送方式。
编辑:Java HashCode
通常,hashCode()仅在不覆盖对象的情况下返回内存中的对象地址。
有关hashCode
函数,请参见reference。
在合理可行的范围内,由 Object类确实为不同的对象返回不同的整数。 (这个 通常通过转换内部地址来实现 对象转换成整数,但是这种实现技术不是 JavaTM编程语言所要求的。)
因此,Java hashCode
函数的功能与默认的Python __hash__
函数相同。
例如,在Java中,如果您在HashSet
中使用了可变对象,则HashSet
不能正常工作。由于hashCode
取决于对象的状态,因此无法再正确检索它,因此对包含的检查失败。
答案 2 :(得分:1)
从阅读其他评论/答案来看,您似乎不愿意购买的是,在突变时,您必须更改可变实体的哈希,并且您只能用id
进行哈希,所以我'将尽力阐述这一点。
引用您的话
@kindall Hm ...谁说哈希值必须来自列表中的值?那如果你例如添加一个新值,您必须重新哈希该列表,获取一个新的哈希值,等等。在其他语言中,事实并非如此……这是我的观点。在其他语言中,哈希值仅来自id(或者id本身,就像用户定义的可变Python对象一样)...好吧...我只是觉得这会使Python变得有些复杂(尤其是对于初学者...不适合我)。
这不是完全错误的(尽管我不知道您所指的是“其他”语言),您可以这样做,但是会有一些非常可怕的后果:
class HashableList(list):
def __hash__(self):
return id(self)
x = HashableList([1,2,3])
y = HashableList([1,2,3])
our_set = {x}
print("Is x in our_set? ", x in our_set)
print("Is y in our_set? ", y in our_set)
print("Are x and y equal? ", x == y)
(意外)输出:
Is x in our_set? True
Is y in our_set? False <-- potentially confusing
Are x and y equal? True
这意味着散列与相等性不一致,这完全是令人困惑的。
您可能会反驳“好吧,然后按内容散列”,但是我认为您已经理解,如果内容发生更改,则会出现其他不良行为(例如):
class HashableListByContents(list):
def __hash__(self):
return sum(hash(x) for x in self)
a = HashableListByContents([1,2,3])
b = HashableListByContents([1,2,3])
our_set = {a}
print('Is a in our_set? ', a in our_set)
print('Is b in our_set? ', b in our_set)
print('Are a and b equal? ', a == b)
这将输出:
Is a in our_set? True
Is b in our_set? True
Are a and b equal? True
到目前为止一切顺利!但是...
a.append(2)
print('Is a still in our set? ', a in our_set)
此输出:
Is a still in our set? False <-- potentially confusing
我不是Python初学者,所以我不会想知道什么会使Python初学者感到困惑,但无论哪种方式,这似乎都使我感到困惑(至多)。我的两分钱是,哈希可变对象简直是不正确的。我的意思是,我们有功能纯粹主义者声称可变对象是不正确的,时期! Python不会阻止您执行您描述的任何操作,因为它永远不会强制执行这样的范例,但是无论您走了哪条路,它实际上都在麻烦。
HTH!