python如何计算元组的哈希值

时间:2018-04-08 20:01:17

标签: python hash tuples

在python中,如果我有一个包含许多元素的元组,它的哈希值是根据其元素“id s还是其元素”的内容计算的?

在此示例中,

a = (1, [1,2])
hash(a)

错误地说列表不可用。所以我猜它不是由id计算的,或者可能是检查元素是否可变。

现在看这个例子

class A: pass
a0 = A()
ta = (1, a0)
hash(ta)  # -1122968024
a0.x = 20
hash(ta)  # -1122968024

事实证明ta的散列不随其元素的修改而改变,即a0。那么也许a0的id用于哈希计算? a0被认为是不可变的吗? python如何知道类型是否可变?

现在考虑一下这个案例

b = (1, 2)
id(b)  # 3980742764
c = (1, 2)
id(c)  # 3980732588
tb = (1, b)
tc = (1, c) 
hash(tb)  # -1383040070
hash(tc)  # -1383040070

似乎bc的内容用于哈希计算。

我应该如何理解这些例子?

4 个答案:

答案 0 :(得分:24)

都不是。它是根据这些元素的散列计算的,而不是它们的“内容”(值/属性)。

python's documentation glossary中查看此段落。

是否可以哈希,如何哈希值,取决于其.__hash__()方法的实现。 Python本身并不知道对象的可变性。

在您的第一个示例中,tuple碰巧根据其元素散列自身,而list根本没有散列 - .__hash__()方法未实现因为它(并有充分理由)。这就是为什么tuple内部带有list对象的__eq__()不可用的。

现在,考虑到这一点,让我们看看python data model documentation,以及它对该主题的看法:

  

默认情况下,用户定义的类具有__hash__()x.__hash__()方法;与它们相比,所有对象都比较不相等(除了自己),x == y返回一个适当的值,x is y同时暗示hash(x) == hash(y).__hash__()

这就是为什么你不必为你的类定义hash() - 在这种情况下python为你做了。默认实现不会考虑实例字段。这就是为什么你可以在不改变其哈希值的情况下改变对象内部的值。

在这方面你是对的 - 自定义类的哈希函数的默认( CPython的)实现依赖于对象的id(),而不是内部的值。它。它是一个实现细节,但它在Python版本之间有所不同。在更新的Python版本中,id()static Py_hash_t tuplehash(PyTupleObject *v)之间的关系涉及一些随机化。

但是它实际上如何散列呢?

虽然细节非常复杂并且可能涉及一些高级数学,但是元组对象的散列函数的实现是用C语言编写的,可以看作here(参见y = PyObject_Hash(*p++);

计算涉及使用每个元组元素的哈希对常量进行异或运算。负责元素散列的行是这样的:

class User {
  const User(this.id,this.name);

  final String name;
  final int id;
}

所以,回答你原来的问题:它有一堆XOR hokus-pocus,每个元素的哈希。是否使用这些元素的内容取决于它们的特定散列函数。

答案 1 :(得分:8)

哈希的核心契约是相等的对象具有相等的哈希值。特别是,散列并不直接关注可变性或突变;它只关心影响平等比较的突变

你的第一个元组是不可取的,因为改变嵌套列表会改变元组在相等比较中的行为方式。

在第二个示例中变异a0不会影响元组的散列,因为它不会影响相等比较。 a0仍然只与自身相等,其哈希值不变。

第三个示例中的

tbtc具有相等的哈希值,因为它们是相等的元组,无论它们的元素是否是相同的对象。

这意味着元组不能(直接)使用id进行散列。如果他们这样做,具有不同但相同元素的相等元组可能会以不同的方式散列,违反哈希合约。如果没有特殊的外壳元素类型,元组可以用来计算自己的哈希的唯一东西就是它们的元素'哈希,所以元组将他们的哈希基于他们的元素'散列。

答案 2 :(得分:3)

问题的答案“是否根据身份或价值计算元组的哈希?”是:都不是。

正确的答案是元组的哈希值是从元素的哈希值计算出来的。如何计算这些哈希值(或多或少)无关紧要。

证明这一点的一个简单方法是查看将列表放入元组时会发生什么:

>>> hash( (1, 2) )
3713081631934410656
>>> hash( (1, []) )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

由于列表不可清除,因此包含列表的元组也不可清除。

让我们仔细看看你带来的这个例子:

class A: pass
a0 = A()
ta = (1, a0)
hash(ta)  # -1122968024
a0.x = 20
hash(ta)  # -1122968024

为什么不设置a0.x = 20会影响元组的哈希?好吧,如果我们修改此代码以输出a0的哈希值,您会看到设置a0.x = 20a0的哈希值没有影响:

a0 = A()
print(hash(a0))  # -9223363274645980307
a0.x = 20
print(hash(a0))  # -9223363274645980307

原因是python为你实现了一个默认的哈希函数。来自the docs

  

默认情况下,用户定义的类具有__eq__()__hash__()方法;   与他们,所有对象比较不平等(除了他们自己)和   x.__hash__()返回x == y暗示的适当值   x is yhash(x) == hash(y)

默认哈希函数忽略对象的属性,并根据对象的id计算哈希值。无论您对a0做出哪些更改,其哈希值始终保持不变。 (虽然可以通过实现自定义__hash__方法为A类的实例定义自定义哈希函数。)

附录:列表不可清除的原因是因为它们是可变的。来自the docs

  

如果一个类定义了可变对象并实现了__eq__()方法,   自从执行以来,它不应该实现__hash__()   hashable集合要求键的哈希值是不可变的(如果   对象的哈希值发生变化,它将在错误的哈希桶中。)

列表属于此类别。

答案 3 :(得分:2)

tuple的哈希基于内容,而不是基于元组的_id_s。散列是以递归方式计算的:如果一个元素不可散列(如list元素),则元组本身不可散列。

如果ab是元组和a == b,那么hash(a) == hash(b)(如果当然可以计算哈希值),那就完全正常了,即使a is not b

(相反hash(a) == hash(b)并不代表a == b

is传达的信息通常不是很有用,因为例如python对象实习。