如何在Python3中组合哈希码?

时间:2015-04-03 15:57:26

标签: python-3.x hashcode

我更熟悉从子类中的超类构建复杂/组合哈希码的“Java方法”。 Python 3中有更好/不同/首选的方式吗? (我无法通过Google在此问题上找到任何特定于Python3的内容。)

class Superclass:
    def __init__(self, data):
        self.__data = data

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

class Subclass(Superclass):
    def __init__(self, data, more_data):
        super().__init__(data)
        self.__more_data = more_data

    def __hash__(self):
        # Just a guess...
        return hash(super()) + 31 * hash(self.__more_data)

为简化此问题,请假设self.__dataself.__more_data是简单易用的数据,例如strint

5 个答案:

答案 0 :(得分:8)

产生良好哈希的最简单方法是将值放入标准的可哈希处理的Python容器中,然后对 that 进行哈希处理。这包括在子类中组合哈希。我将解释为什么,然后解释如何

基本要求

第一件事:

  • 如果两个对象的测试相等,则它们必须具有相同的哈希值
  • 具有散列的对象 必须 随着时间的推移会产生相同的散列

只有遵循这两个规则,您的对象才能安全地用于词典和集合中。哈希表不变,是防止字典和集合中断的原因,因为字典和集合使用哈希表来选择存储位置,并且如果另一个对象测试哈希表是否更改,则无法再次定位该对象。

请注意,两个对象的类型不同甚至都没有关系。 True == 1 == 1.0,因此它们都具有相同的哈希值,并且都将被视为字典中的相同键。

什么才是好的哈希值

您希望将对象值的组成部分组合起来,以尽可能为不同的值生成不同的哈希值。其中包括 ordering 特定含义之类的内容,因此代表属性值不同方面的两个属性(可容纳相同类型的Python对象)仍然导致不同的结果散列,大部分时间

请注意,如果两个表示不同值(不会相等)的对象具有相等的哈希值,则为 fine 。重用哈希值不会破坏集合或字典。但是,如果许多不同的对象值产生相等的散列,则这会降低它们的效率,因为您增加了碰撞的可能性。冲突需要collision resolution,而冲突解决需要花费更多时间,因此您可以use denial of service attacks on servers with predictable hashing implementations(*)

因此,您希望广泛散布可能的散列值。

要注意的陷阱

documentation for the object.__hash__ method包含有关如何组合值的一些建议:

  

唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式混合在一起(例如,使用“异或”或)对象组成部分的哈希值,这些哈希值在对象比较中也起着重要作用。

但仅使用XOR的 不会产生良好的哈希值,当与您进行XOR运算的哈希值的类型可以相同但含义不同时(取决于已为其分配的属性)至。举例说明:

>>> class Foo:
...     def __init__(self, a, b):
...         self.a = a
...         self.b = b
...     def __hash__(self):
...         return hash(self.a) ^ hash(self.b)
...
>>> hash(Foo(42, 'spam')) == hash(Foo('spam', 42))
True

由于self.aself.b的哈希值只是进行异或运算,因此对于任何顺序我们都具有相同的哈希值,因此有效地将可用哈希值的数量减半。通过使用更多属性来执行此操作,您可以快速减少唯一哈希的数量。因此,如果可以在组成哈希的不同元素中使用相同的值,则可能需要在哈希中包含有关每个属性的更多信息。

接下来,请注意,尽管Python整数是无界的,但哈希值不是。也就是说,哈希值具有有限范围。来自同一文档:

  

注意hash()将对象的自定义__hash__()方法返回的值截断为Py_ssize_t的大小。在64位版本上通常为8个字节,在32位版本上通常为4个字节。

这意味着,如果您使用加法或乘法或其他操作来增加存储哈希值所需的位数,则最终将丢失高位,因此再次减少了不同哈希值的数量。

接下来,如果您将多个散列与已经具有有限范围的XOR组合在一起,则最终可能会得到更少的散列。举一个极端的例子,尝试对0至10范围内的1000个随机整数的散列进行XOR运算。

哈希,简单的方法

Python开发人员长期以来一直在努力解决上述陷阱,并针对标准库类型进行了解决。利用这个优势。 将您的值放入一个元组,然后对该元组进行哈希处理。

Python元组使用xxHash algorithm的简化版本来捕获订单信息并确保广泛的哈希值。因此,对于不同的属性,可以通过在元组中赋予它们不同的位置,然后对元组进行散列来捕获不同的含义:

def __hash__(self):
    return hash((self.a, self.b))

这可确保您获得用于唯一顺序的唯一哈希值。

如果您要对某个子类进行子类化,则将父实现的哈希值放入元组位置之一:

def __hash__(self):
    return hash((super().__hash__(), self.__more_data))

哈希值的确会将其减小为60位或30位的值(分别在32位或64位平台上),但是与元组中的其他值组合时,这并不是一个大问题。如果您真的对此感到担心,请将None放在元组中作为占位符,然后对父哈希值(即super().__hash__() ^ hash((None, self.__more_data)))进行XOR。但这确实太过分了。

如果您有多个值,它们的相对顺序无关紧要,并且不想将所有这些值一一进行异或,请考虑使用frozenset()对象进行快速处理,如果值不是唯一的,则与collections.Counter()对象结合使用。 frozenset()散列操作通过首先对散列中的位进行重新组合来解决较小的散列范围:

# unordered collection hashing
from collections import Counter
hash(frozenset(Counter(...).items()))

考虑使用数据类

对于大多数为其编写__hash__函数的对象,您实际上想使用dataclass generated class

from dataclasses import dataclass
from typing import Union

@dataclass(frozen=True)
class Foo:
    a: Union[int, str]
    b: Union[int, str]
使用所有字段值中的__hash__,在frozen=Trueunsafe_hash=True时,为

数据类提供合理的tuple()实现。


(*) Python通过使用整个进程的random hash seed对字符串,字节和datetime对象进行哈希处理,来保护您的代码免受此类哈希冲突攻击。

答案 1 :(得分:4)

The python documentation建议你使用xor来组合哈希:

  

唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式将对象组件的哈希值混合在一起(例如使用exclusive或),这些哈希值也是对象比较中的一部分。

我还建议xor而不是加法和乘法因为:

  

注意

     

hash()会将对象的自定义__hash__()方法返回的值截断为Py_ssize_t的大小。这通常是64位版本上的8个字节和32位版本上的4个字节。如果对象的__hash__()必须在不同位大小的构建上进行互操作,请务必检查所有支持的构建的宽度。一个简单的方法是使用python -c "import sys; print(sys.hash_info.width)"

顺便说一句,对于python 2.7和python 3.4,这个文档是相同的。

答案 2 :(得分:1)

我正在补充补充Thom的答案。

  1. Python整数从不溢出;他们“无限地”扩张;它们不像大多数其他语言那样限制为32位或64位。因此,经典模式x + 31 * hash(value)将产生大量整数。
  2. Python哈希码应限制为8个字节(64位)。因此,使用按位XOR运算符^来组合哈希码更安全。
  3. 示例代码应该读取:

    class Superclass:
        def __init__(self, data):
            self.__data = data
    
        def __hash__(self):
            return hash(self.__data)
    
    class Subclass(Superclass):
        def __init__(self, data, more_data):
            super().__init__(data)
            self.__more_data = more_data
    
        def __hash__(self):
            return super().__hash__() ^ hash(self.__more_data)
    

答案 3 :(得分:0)

使用元组,而不是将多个字符串组合在一起,因为它们在python中是可哈希的。

t: Tuple[str, str, int] = ('Field1', 'Field2', 33)
print(t.__hash__())

这将使代码也更易于阅读。

答案 4 :(得分:-1)

对于任何阅读此书的人来说,对XORing哈希值都不是一个好主意,因为特定的重复哈希值序列可以一起对XOR进行XOR,并有效地从哈希值集中删除元素。

例如:

(hash('asd') ^ hash('asd') ^ hash('derp')) == hash('derp')

甚至:

(hash('asd') ^ hash('derp') ^ hash('asd')) == hash('derp')

因此,如果您正在使用此技术来确定组合哈希中是否有一组特定的值,则可能会在其中将重复的值添加到哈希中,使用XOR可能会导致该值从集合中删除。取而代之的是,您应该考虑AND,它具有避免与前一个张贴者提到的无界整数增长相同的属性,但要确保不删除重复项。

(hash('asd') & hash('asd') & hash('derp')) != hash('derp')

如果您想进一步探索,应该查看Bloom过滤器。