Python对象的哈希何时计算,为什么-1的哈希不同?

30 投票
3 回答
5069 浏览
提问于 2025-04-17 03:38

接着之前的问题,我想知道一个Python对象的哈希值到底是什么时候被计算的?

  1. 在实例的 __init__ 方法执行的时候,
  2. 第一次调用 __hash__() 方法的时候,
  3. 每次调用 __hash__() 方法的时候,还是
  4. 还有其他我可能遗漏的情况?

这个计算的时机会根据对象的类型而有所不同吗?

为什么 hash(-1) == -2,而其他整数的哈希值却和它们自己相等呢?

3 个回答

1

我们可以很容易地看出,选项 #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
8

来自 这里

哈希值 -1 是保留的(它用于在 C 语言实现中标记错误)。如果哈希算法生成这个值,我们就直接用 -2 来代替。

因为整数的哈希值本身就是整数,所以它会直接被更改。

30

哈希值通常在每次使用时都会计算,你可以很容易地自己检查一下(见下文)。当然,某些特定的对象可以选择缓存它的哈希值。例如,CPython中的字符串会这样做,但元组就不会(具体原因可以参考这个被拒绝的bug报告)。

在CPython中,哈希值-1 表示出错。这是因为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

撰写回答