何时是python对象';s哈希已计算,为什么-1的哈希不同?

何时是python对象';s哈希已计算,为什么-1的哈希不同?,python,hash,Python,Hash,从问题开始,我想知道python对象的哈希是什么时候计算的 在实例的\uuuu init\uuuu时间 第一次调用\uuuu hash\uuuu() 每次调用\uuuu hash\uuuu()时,或 我还错过了其他机会吗 这可能因对象的类型而异吗 为什么散列(-1)=-2而其他整数等于它们的散列?来自: 哈希值-1是保留的(用于标记C实现中的错误)。 如果哈希算法生成这个值,我们只需使用-2即可 由于integer的哈希值是integer本身,因此它会立即更改。哈希值通常在每次使用时计算,因为您

从问题开始,我想知道python对象的哈希是什么时候计算的

  • 在实例的
    \uuuu init\uuuu
    时间
  • 第一次调用
    \uuuu hash\uuuu()
  • 每次调用
    \uuuu hash\uuuu()
    时,或
  • 我还错过了其他机会吗
  • 这可能因对象的类型而异吗

    为什么
    散列(-1)=-2
    而其他整数等于它们的散列?

    来自:

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


    由于integer的哈希值是integer本身,因此它会立即更改。

    哈希值通常在每次使用时计算,因为您可以很容易地检查自己(请参见下文)。 当然,任何特定对象都可以自由缓存其哈希。例如,CPython字符串可以做到这一点,但元组不能做到这一点(参见示例,了解原因)

    CPython中的哈希值为-1。这是因为C没有异常,所以它需要使用返回值。当Python对象的
    \uuuuu散列\uuuuu
    返回-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
    

    很容易看出#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
    子类化,则结果可能不是不可变的,因此每次都会再次调用
    \uuuuuuuuuuu散列
    方法。元组通常被认为是不可变的,因此您可能认为可以缓存哈希,但实际上元组的哈希取决于其内容的哈希,并且可能包含可变值

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

    >>> 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
    

    3已退出,因为它在第一次调用时被缓存。我认为第二个选项是正确的,但我不确定我不会把它作为一个答案发布:)@rplnt:error;那只是指字典。它的散列将存储在字典中,但一般的散列不是这样。@ChrisMorgan实际上我不认为python
    dict
    为它的键缓存散列值。当然,个别类可以在其
    \uuuuu hash\uuuuu
    函数中执行任何它们喜欢的操作,因此上面引用的文章说
    str
    缓存其哈希值。@max:
    dict
    是一个哈希表,根据定义必须存储其键的哈希值。这就是我所指的。“我所说的完全是真的。”克里斯姆博格当然同意。最初,我以为您是说
    dict
    存储从键到其散列的映射,从而避免了重复调用
    hash(k)
    的必要性——这一点不会发生。如果将包含可变值的元组作为参数传递给内置散列函数,则会引发TypeError异常。所以这不是元组不缓存其哈希值的原因。本章开头的链接提供了说明。另见。另外,您确定str子类没有缓存哈希吗?它似乎返回与str.hash相同的值,该值被自动缓存。@max,我为您添加了一个示例,说明
    str
    子类的哈希值未被缓存。啊,是的,对。。我想我在想如果你不定义
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。