题 什么时候计算python对象的哈希值,为什么-1的哈希值不同?


继续之后 这个 问题,我很想知道什么时候是python对象的哈希 计算

  1. 在一个实例的 __init__ 时间,
  2. 第一次 __hash__() 叫做,
  3. 每次 __hash__() 被称为,或
  4. 我可能会失踪的任何其他机会?

这可能会根据对象的类型而有所不同吗?

为什么 hash(-1) == -2 而其他整数等于它们的哈希值?


23
2017-10-04 12:48


起源


3就像我一样 读 它在第一次被调用时被缓存。我会假设第二个选项是正确的,但我不确定我不会发布它作为答案:) - rplnt
@rplnt:错了;那只是在谈论字典时。它的哈希值将存储在字典中,但对于一般哈希则不然。 - Chris Morgan
@ChrisMorgan其实我不觉得python dict 缓存其键的哈希值。当然,个别班级可以做他们喜欢的任何事情 __hash__ 功能,所以上面引用的文章说 str 缓存其哈希值。 - max
@Max: dict 是一个哈希表,必须按照定义存储其键的哈希值。这就是我所说的。我所说的完全正确。 - Chris Morgan
@ChrisMorgan当然同意了。最初,我以为你是这么说的 dict 存储从键到其哈希的映射,从而避免调用的必要性 hash(k)反复 - 这不会发生AFAIK。 - max


答案:


哈希值通常在每次使用时计算,因为您可以很容易地检查自己(见下文)。 当然,任何特定对象都可以自由地缓存其哈希值。例如,CPython字符串执行此操作,但元组不执行此操作(请参阅例如 这被拒绝的错误报告 原因)。

哈希值-1 发出错误信号 在CPython中。这是因为C没有异常,所以需要使用返回值。当一个Python对象的时候 __hash__ 返回-1,CPython实际上会默默地将其更改为-2。

你自己看:

class HashTest(object):
    def __hash__(self):
        print('Yes! __hash__ was called!')
        return -1

hash_test = HashTest()

# All of these will print out 'Yes! __hash__ was called!':

print('__hash__ call #1')
hash_test.__hash__()

print('__hash__ call #2')
hash_test.__hash__()

print('hash call #1')
hash(hash_test)

print('hash call #2')
hash(hash_test)

print('Dict creation')
dct = {hash_test: 0}

print('Dict get')
dct[hash_test]

print('Dict set')
dct[hash_test] = 0

print('__hash__ return value:')
print(hash_test.__hash__())  # prints -1
print('Actual hash value:')
print(hash(hash_test))  # prints -2

21
2017-10-04 13:19





这里

哈希值-1是保留的(它用于标记C实现中的错误)。   如果哈希算法生成此值,我们只需使用-2代替。

由于整数的哈希是整数本身,它只是立即改变。


6
2017-10-04 13:18





很容易看出选项#3适用于用户定义的对象。如果您改变对象,这允许散列变化,但如果您将对象用作字典键,则必须确保防止散列不断变化。

>>> class C:
    def __hash__(self):
        print("__hash__ called")
        return id(self)


>>> inst = C()
>>> hash(inst)
__hash__ called
43795408
>>> hash(inst)
__hash__ called
43795408
>>> d = { inst: 42 }
__hash__ called
>>> d[inst]
__hash__ called

字符串使用选项#2:它们计算一次哈希值并缓存结果。这是安全的,因为字符串是不可变的,因此哈希永远不会改变,但如果你是子类 str 结果可能不是一成不变的 __hash__ 每次都会调用方法。元组通常被认为是不可变的,所以你可能认为哈希可以被缓存,但实际上元组的哈希依赖于其内容的哈希值,并且可能包含可变值。

对于那些不相信子类的@max str 可以修改哈希:

>>> class C(str):
    def __init__(self, s):
        self._n = 1
    def __hash__(self):
        return str.__hash__(self) + self._n


>>> x = C('hello')
>>> hash(x)
-717693723
>>> x._n = 2
>>> hash(x)
-717693722

1
2017-10-04 14:39



如果将包含可变值的元组作为参数传递给内置哈希函数,则会引发TypeError异常。所以这不是元组不缓存哈希值的原因。开头的链接 @ PetrViktorin上面的回答 提供解释。也可以看看 圭多的评论。另外,你确定str子类没有缓存哈希吗?它似乎返回与str.hash相同的值,它会自动缓存。 - max
@max,我为你添加了一个示例,显示了一个哈希值 str 子类未缓存。 - Duncan
啊是的,对..我想我在考虑你是不是要定义 __hash__,它被缓存但是它很明显,因为它只是使用 str.__hash__ 在这种情况下。 - max