用于任意对象的python哈希函数的替代方法

用于任意对象的python哈希函数的替代方法,python,python-3.x,python-2.x,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 __get

在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()
使用了随机或固定的salt,这使得它不可用/不太理想[1]。显然,不可能使用一个。因此,我需要一个替代方案:

  • 必须在解释器调用中保持稳定
  • 可能需要在执行时提供参数,例如在调用中设置salt
  • 必须支持任意对象(dict所支持的任何对象)
我已经尝试过使用来自(slow!)的哈希函数和来自(显然不适合哈希,但meh)的校验和,它们可以很好地处理字符串/字节。但是,它们只适用于类似字节的对象,而
hash()
几乎适用于所有对象


[1] 使用
hash()

  • 如果盐是随机的,则在解释器调用中不可靠
  • 如果盐是固定的,则防止应用程序使用随机盐析功能
  • 如果使用不同的盐创建了两个
    PersistentDict
    s,则不可用

我成功地使用了
散列和
zlib.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
在Python3.4.3中,这比调用普通的
散列
要慢得多,大约需要0.07usec。对于常规对象,
hashkey
采用~1.0usec。0.8 usec表示
字节
,0.7表示
str

间接费用大致如下:

  • 0.1函数调用使用C(
    hash(obj)
    vs
    def pyhash(obj):返回hash(obj)
  • 0.2 usec到0.5 usec,用于通过
    isinstance
  • 对于
    zlib.adler32
    zlib.crc32
    vs
    hash
    :~0.160 usec vs~0.75 usec(adler和crc为+/-4 usec)
  • 0.15用于
    str
    对象的
    obj.encode()
    “foobar”
  • 1.5用于
    datetime.datetime
    对象的
    str(obj).encode()
最大的优化来自
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)}
总时间:
str
bytes
约为0.7usec,
datetime
非常糟糕,
对象、整数等约为0.35usec。如果单独对
dict
键(aka类型)进行显式检查,则使用
dict
将类型映射为hash compariable(即hashkey.dict类型中不是
obj.\uu类
,而是hashkey.explicit.\u dict类型中的
obj.\uu类


一些补充说明:

  • hash
    对于使用默认
    \uuuuu hash\uuuuu
    实现的任何对象,在解释器启动期间都不稳定,包括
    None
  • 对于包含salted类型的不可变容器(定义
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

我成功地使用了
散列和
zlib.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中,这比调用普通的
散列
要慢得多,它大约需要0.07 usec。对于常规对象,
散列键
需要~1.0 usec。0.8 usec用于
字节
和0.7用于
str

间接费用大致如下:

  • 0.1函数调用使用C(
    hash(obj)
    vs
    def pyhash(obj):返回hash(obj)
  • 0.2 usec到0.5 usec,用于通过
    isinstance
  • 对于
    zlib.adler32
    zlib.crc32
    vs
    hash
    :~0.160 usec vs~0.75 usec(adler和crc为+/-4 usec)
  • 0.15用于
    str
    对象的
    obj.encode()
    “foobar”
  • 1.5用于
    datetime.datetime
    对象的
    str(obj).encode()
最大的优化来自于对
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)}
总时间:
str
bytes
约为0.7usec,
datetime
非常糟糕,
对象、整数等约为0.35usec。如果单独对
dict
键(aka类型)进行显式检查,则使用
dict
将类型映射为hash compariable(即hashkey.dict类型中不是
obj.\uu类
,而是hashkey.explicit.\u dict类型中的
obj.\uu类


一些补充说明:

  • hash
    对于使用默认
    \uuuuu hash\uuuuu
    实现的任何对象,在解释器启动期间都不稳定,包括
    None
  • 对于包含salted类型的不可变容器(定义
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

随机散列只适用于
str
bytes
datetime
对象。对于这些类型,您只需要一个替代对象即可。@MartijnPieters谢谢!我想str和bytes可以被zlib/hashlib覆盖。我会看看是否能找到datetime的快速方法。如果时间允许,我仍然会彻底测试并寻找替代方法然而,涉及到区域;我怀疑时区的微妙之处可能会导致不同时区对象的ISO8601表示形式相同,这可能很重要。对于
None
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>实现是从
type
继承的从一个解释器到另一个解释器进程,该值可以随机出现。这意味着设置
pythonhasheed
不会影响