Python对象的哈希何时计算,为什么-1的哈希不同?
接着之前的问题,我想知道一个Python对象的哈希值到底是什么时候被计算的?
- 在实例的
__init__
方法执行的时候, - 第一次调用
__hash__()
方法的时候, - 每次调用
__hash__()
方法的时候,还是 - 还有其他我可能遗漏的情况?
这个计算的时机会根据对象的类型而有所不同吗?
为什么 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
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