替代任意对象的python哈希函数

时间:2016-06-24 09:15:12

标签: python python-3.x python-2.x

在python2.7中,我成功地使用hash()将对象放入持久存储在磁盘上的存储桶中。模型代码如下所示:

class PersistentDict(object):
  def __setitem__(self, key, value):
    bucket_index = (hash(key)&0xffffffff) % self.bucket_count
    self._store_to_bucket(bucket_index, key, value)

  def __getitem__(self, key):
    bucket_index = (hash(key)&0xffffffff) % self.bucket_count
    return self._fetch_from_bucket(bucket_index)[key]

在python3中,hash()使用随机或固定的盐,这使得它不可用/次优[1]。显然,不可能使用fixed salt for specific invocations。所以,我需要一个替代方案:

  • 必须在解释器调用之间保持稳定
  • 可能需要在执行时提供的参数,例如在通话中设置盐
  • 必须支持任意对象(dict / set支持的任何内容)

我已经尝试过使用来自hashlib的哈希函数(慢!)和来自zlib的校验和(显然不适合散列,但是meh),它们可以很好地处理字符串/字节。但是,它们仅在类似字节的对象上 ,而hash()几乎适用于所有内容。

[1]使用hash()识别存储桶是:

  • 如果盐是随机的,则在解释器调用中不可靠
  • 如果盐已修复,则阻止应用程序使用随机腌制功能
  • 如果使用不同的盐创建了两个PersistentDict,则无法使用

1 个答案:

答案 0 :(得分:0)

我使用hashzlib.adler32的组合取得了成功。最直接的实现是:

def hashkey(obj, salt=0):
  """
  Create a key suitable for use in hashmaps

  :param obj: object for which to create a key
  :type: str, bytes, :py:class:`datetime.datetime`, object
  :param salt: an optional salt to add to the key value
  :type salt: int
  :return: numeric key to `obj`
  :rtype: int
  """
  if obj is None:
    return 0
  if isinstance(obj, str):
    return zlib.adler32(obj.encode(), salt) & 0xffffffff
  elif isinstance(obj, bytes):
    return zlib.adler32(obj, salt) & 0xffffffff
  elif isinstance(obj, datetime_type):
    return zlib.adler32(str(obj).encode(), salt) & 0xffffffff
  return hash(obj) & 0xffffffff

使用Python 3.4.3,这比调用普通hash要慢得多,后者需要大约0.07 usec。对于常规对象,hashkey需要~1.0 usec。 0.8 usec表示bytes,0.7表示str

开销大致如下:

  • 0.1 usec用于函数调用(hash(obj) vs def pyhash(obj): return hash(obj)
  • 0.2 usec到0.5 usec,用于通过isinstance
  • 选择哈希函数
  • 0.75 usec用于zlib.adler32zlib.crc32 vs hash:~0.160 usec vs~0.75 usec(adler和crc为+/- 4 usec)
  • obj.encode() str"foobar"个对象str(obj).encode()的0.15 usec
  • 1.5 datetime.datetime if个对象
  • 的usec

最优化来自def hashkey_c(obj, salt=0): if obj.__class__ in hashkey_c.types: if obj is None: return 0 if obj.__class__ is str: return zlib.adler32(obj.encode(), salt) & 0xffffffff elif obj.__class__ is bytes: return zlib.adler32(obj, salt) & 0xffffffff elif obj.__class__ is datetime_type: return zlib.adler32(str(obj).encode(), salt) & 0xffffffff return hash(obj) & 0xffffffff hashkey_c.types = {str, bytes, datetime_type, type(None)} 语句的排序。如果一个人主要期望普通物体,以下是我能想到的最快的物品:

str

总时间:bytesdatetime约为0.7,dict为极差,对象为int,等等为0.35。使用dict将地图类型映射到如果一个人使用obj.__class__ in hashkey.dict_types密钥(又称类型)分别使用显式检查(即不是obj.__class__ in hashkey.explicit_dict_types而是hash),则哈希值可比较。

一些补充说明:

    对于使用默认__hash__实施的任何对象,
  • None在解释器启动时不稳定,包括__hash__
  • 对于包含salted类型的不可变容器(定义{{​​1}}),它无法正常工作,例如(1, 2, 'three')