【问题标题】:Alternative to python hash function for arbitrary objects任意对象的python散列函数的替代方案
【发布时间】:2016-06-24 09:15:12
【问题描述】:

在 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() 识别存储桶是:

  • 如果盐是随机的,则在解释器调用中不可靠
  • 防止应用程序使用随机加盐功能(如果盐是固定的)
  • 如果使用不同的盐创建两个 PersistentDicts,则无法使用

【问题讨论】:

  • 随机哈希仅适用于strbytesdatetime对象。您只需要这些类型的替代品。
  • @MartijnPieters 谢谢!我猜 str 和 bytes 可以被 zlib/hashlib 覆盖。我看看能不能快速找到日期时间的东西。
  • 如果涉及时区,我仍然会彻底测试并寻找替代方案;我怀疑时区的细微差别可能会导致其他不同时区对象的 ISO8601 表示形式相同,这可能很重要。
  • 对于None__hash__ 的实现继承自type,它产生对象内存地址的哈希值,因为这因解释器进程而异,该值可能出现随机.这意味着设置PYTHONHASHSEED 不会影响None 的哈希值,就像它适用于种子的类型一样。
  • 所以我想我必须修改我的第一条评论以包括类型对象(自定义类和内置)和None

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


【解决方案1】:

我已经成功使用了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 微秒。对于常规对象,hashkey 需要 ~1.0 微秒。 bytes 为 0.8 微秒,str 为 0.7 微秒。

开销大致如下:

  • 0.1 微秒用于函数调用(hash(obj)def pyhash(obj): return hash(obj)
  • 0.2 微秒到 0.5 微秒用于通过isinstance 选择散列函数
  • zlib.adler32zlib.crc32hash 为 0.75 微秒:~0.160 微秒 vs 约 0.75 微秒(adler 和 crc 为 +/- 4 微秒)
  • obj.encode()str 对象 ("foobar") 为 0.15 微秒
  • str(obj).encode() of datetime.datetime 对象为 1.5 微秒

最大的优化来自if 语句的排序。如果人们主要期望普通对象,那么以下是我能想到的最快的:

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

总时间:strbytes 约 0.7 微秒,datetime 极差,对象、整数等为 0.35 微秒。使用 dict 将类型映射到哈希可比较,如果使用显式分别检查dict 键(又名类型)(即不是obj.__class__ in hashkey.dict_types,而是obj.__class__ in hashkey.explicit_dict_types)。


一些补充说明:

  • 对于使用默认 __hash__ 实现的任何对象,hash 在解释器启动时不稳定,包括 None
  • 它不适用于包含盐渍类型的不可变容器(定义 __hash__),例如(1, 2, 'three')

【讨论】:

  • 从 python3.6 开始,这不起作用。它在不同的解释器执行中不稳定。例如。尝试哈希键(无)。
  • @NealYoung 感谢您的检查。似乎hash(None) 至少从 Python3.4 开始就被加盐了——我现在已经添加了这个案例。我还意识到像 Tuple 这样的可散列容器也需要特殊的大小写。
猜你喜欢
  • 2023-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
相关资源
最近更新 更多