实现__hash __()的正确和好方法是什么?

use*_*898 126 python dictionary hashtable hashcode

什么是正确和好的实施方式__hash__()

我在谈论返回哈希码的函数,该哈希码随后用于将对象插入哈希表,即字典.

__hash__()返回一个整数并用于将对象"分箱"为哈希表时,我假设返回的整数的值应该为公共数据均匀分布(以最小化冲突).获得这些价值观的好习惯是什么?碰撞是一个问题吗?在我的例子中,我有一个小类,它充当一个容器类,包含一些int,一些浮点数和一个字符串.

Joh*_*kin 156

一种简单,正确的实现方法__hash__()是使用密钥元组.它不会像专门的哈希那么快,但如果你需要那么你应该在C中实现类型.

这是使用密钥进行哈希和相等的示例:

class A:
    def __key(self):
        return (self.attr_a, self.attr_b, self.attr_c)

    def __hash__(self):
        return hash(self.__key())

    def __eq__(self, other):
        if isinstance(other, A):
            return self.__key() == other.__key()
        return NotImplemented
Run Code Online (Sandbox Code Playgroud)

此外,文档中__hash__包含更多信息,这些信息在某些特定情况下可能很有价值.

  • 除了分解出“__key”函数的微小开销之外,这几乎与任何哈希一样快。当然,如果已知属性是整数,并且属性不是太多,我想您可能会使用一些本地滚动的哈希来“稍微”运行得更快,但它的分布可能不会那么好。`hash((self.attr_a, self.attr_b, self.attr_c))` 将出奇地快(并且*正确*),因为小 `tuple` 的创建是经过专门优化的,并且它推动了获取的工作并将哈希值组合到 C 内置函数中,这通常比 Python 级别的代码更快。 (2认同)
  • 正如@loved.by.Jesus 下面的回答提到的,不应为可变对象定义/覆盖哈希方法(默认定义并使用 id 进行相等和比较)。 (2认同)

mil*_*dev 21

John Millikin提出了类似的解决方案:

class A(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        return (isinstance(othr, type(self))
                and (self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))

    def __hash__(self):
        return hash((self._a, self._b, self._c))
Run Code Online (Sandbox Code Playgroud)

这个解决方案的问题在于hash(A(a, b, c)) == hash((a, b, c)).换句话说,哈希与其关键成员的元组的哈希冲突.也许这在实践中经常无关紧要?

对Python文档__hash__建议使用类似XOR的子组件的哈希值相结合,这给了我们这样的:

class B(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        if isinstance(othr, type(self)):
            return ((self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))
        return NotImplemented

    def __hash__(self):
        return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
                hash((self._a, self._b, self._c)))
Run Code Online (Sandbox Code Playgroud)

奖金:^ hash((self._a, self._b, self._c))在那里投入更加强大,以获得良好的衡量标准.

更新:正如Blckknght指出的那样,改变a,b和c的顺序可能会导致问题.我添加了一个额外的^ hash(...)来捕获正在散列的值的顺序._a如果要组合的值不能重新排列(例如,如果它们具有不同的类型,因此_b永远不会将值分配给_chash(A(a, b, c)) == hash((a, b, c))等),则可以删除此最终结果.

  • 一个想法:关键元组可以包括类类型,这将阻止具有相同键属性集的其他类被显示为相等:`hash((type(self),self._a,self._b,self. _c))`. (7认同)
  • 您通常不希望将属性直接异或,因为如果更改值的顺序,则会发生冲突.也就是说,`hash(A(1,2,3))`将等于`hash(A(3,1,2))`(并且它们都将哈希等于任何其他`A`实例"1","2"和"3"的排列作为其值.如果你想避免你的实例具有与其参数元组相同的哈希值,只需创建一个sentinel值(作为一个类变量,或者作为一个全局变量),然后将它包含在要进行哈希处理的元组中:return hash((_ sentinel ,self._a,self._b,self._c)) (5认同)
  • 除了使用B代替type(self)之外,在__eq__而不是False遇到意外类型时,通常还建议返回NotImplemented更好的做法。这允许*其他*用户定义的类型实现知道__B的__eq__,并且可以根据需要进行比较。 (2认同)

Geo*_*lly 17

Microsoft Research的Paul Larson研究了各种哈希函数.他告诉我

for c in some_string:
    hash = 101 * hash  +  ord(c)
Run Code Online (Sandbox Code Playgroud)

对各种各样的琴弦都做得非常好.我发现类似的多项式技术可以很好地计算不同子场的散列.

  • 显然Java以相同的方式做到了,但使用31而不是101 (8认同)
  • 对于具有32位环绕乘法的字符串,Python使用`(hash*1000003)XOR ord(c)`.[引文](http://effbot.org/zone/python-hash.htm)] (3认同)
  • 使用这些数字背后的理由是什么?是否有理由选择101或31? (2认同)
  • 这是素数乘数的解释:http://stackoverflow.com/questions/3613102/why-use-a-prime-number-in-hashcode。根据 Paul Larson 的实验,101 似乎工作得特别好。 (2认同)
  • 即使这是真的,在这种情况下也没有实际用处,因为内置的Python字符串类型已经提供了一个__hash__方法。我们不需要自己动手。问题是如何为典型的用户定义类(带有指向内置类型或其他此类用户定义类的属性的属性)实现__hash__,而该答案根本无法解决。 (2认同)

Mik*_*e T 6

实现哈希(以及列表、字典、元组)的一个好方法是通过使用 使其可迭代,从而使对象具有可预测的项目顺序__iter__。因此,修改上面的示例:

class A:

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __iter__(self):
        yield "a", self._a
        yield "b", self._b
        yield "c", self._c

    def __hash__(self):
        return hash(tuple(self))

    def __eq__(self, other):
        return (isinstance(other, type(self))
                and tuple(self) == tuple(other))
Run Code Online (Sandbox Code Playgroud)

(这里__eq__不需要hash,但是很容易实现)。

现在添加一些可变成员来看看它是如何工作的:

a = 2; b = 2.2; c = 'cat'
hash(A(a, b, c))  # -5279839567404192660
dict(A(a, b, c))  # {'a': 2, 'b': 2.2, 'c': 'cat'}
list(A(a, b, c))  # [('a', 2), ('b', 2.2), ('c', 'cat')]
tuple(A(a, b, c)) # (('a', 2), ('b', 2.2), ('c', 'cat'))
Run Code Online (Sandbox Code Playgroud)

只有当您尝试将不可散列的成员放入对象模型中时,事情才会崩溃:

hash(A(a, b, [1]))  # TypeError: unhashable type: 'list'
Run Code Online (Sandbox Code Playgroud)