哈希字典?

问题描述 投票:123回答:11

出于缓存目的,我需要从dict中存在的GET参数生成缓存键。

目前我正在使用sha1(repr(sorted(my_dict.items())))sha1()是一种在内部使用hashlib的便捷方法)但我很好奇是否有更好的方法。

python hash dictionary
11个回答
94
投票

如果您的字典没有嵌套,您可以使用dict的项目进行冻结并使用hash()

hash(frozenset(my_dict.items()))

这比计算JSON字符串或字典表示的计算密集程度要低得多。

更新:请参阅下面的评论,为什么这种方法可能无法产生稳定的结果。


0
投票

您可以使用maps库来执行此操作。具体来说,maps.FrozenMap

import maps
fm = maps.FrozenMap(my_dict)
hash(fm)

要安装maps,只需执行以下操作:

pip install maps

它也处理嵌套的dict案例:

import maps
fm = maps.FrozenMap.recurse(my_dict)
hash(fm)

免责声明:我是maps图书馆的作者。


-8
投票

我是这样做的:

hash(str(my_dict))

112
投票

使用sorted(d.items())不足以让我们获得稳定的代表。 d中的一些值也可能是字典,它们的键仍然会以任意顺序出现。只要所有键都是字符串,我更喜欢使用:

json.dumps(d, sort_keys=True)

也就是说,如果哈希需要在不同的机器或Python版本之间保持稳定,我不确定这是否是防弹的。您可能希望添加separatorsensure_ascii参数,以保护自己免受对默认值的任何更改。我很感激评论。


57
投票

编辑:如果你的所有键都是字符串,那么在继续阅读这个答案之前,请参阅杰克奥康纳的重要simpler (and faster) solution(这也适用于散列嵌套字典)。

虽然答案已被接受,但问题的标题是“哈希蟒蛇字典”,关于该标题的答案是不完整的。 (关于问题的正文,答案是完整的。)

嵌套字典

如果一个人搜索Stack Overflow如何散列字典,那么人们可能偶然发现这个恰当标题的问题,并且如果有人试图对多个嵌套字典进行散列,则不满意。上面的答案在这种情况下不起作用,你必须实现某种递归机制来检索哈希。

这是一个这样的机制:

import copy

def make_hash(o):

  """
  Makes a hash from a dictionary, list, tuple or set to any level, that contains
  only other hashable types (including any lists, tuples, sets, and
  dictionaries).
  """

  if isinstance(o, (set, tuple, list)):

    return tuple([make_hash(e) for e in o])    

  elif not isinstance(o, dict):

    return hash(o)

  new_o = copy.deepcopy(o)
  for k, v in new_o.items():
    new_o[k] = make_hash(v)

  return hash(tuple(frozenset(sorted(new_o.items()))))

奖励:哈希对象和类

散列类或实例时,hash()函数很有用。但是,对于对象,我在散列中发现了一个问题:

class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789

即使在我改变了foo之后,哈希也是一样的。这是因为foo的身份没有改变,所以哈希是一样的。如果你希望foo根据其当前的定义进行不同的散列,那么解决方案就是对实际发生变化的内容进行散列。在这种情况下,__ dict__属性:

class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785

唉,当你试图对类本身做同样的事情时:

print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'

类__dict__属性不是普通字典:

print (type(Foo.__dict__)) # type <'dict_proxy'>

这是一个与之前类似的机制,它将适当地处理类:

import copy

DictProxyType = type(object.__dict__)

def make_hash(o):

  """
  Makes a hash from a dictionary, list, tuple or set to any level, that 
  contains only other hashable types (including any lists, tuples, sets, and
  dictionaries). In the case where other kinds of objects (like classes) need 
  to be hashed, pass in a collection of object attributes that are pertinent. 
  For example, a class can be hashed in this fashion:

    make_hash([cls.__dict__, cls.__name__])

  A function can be hashed like so:

    make_hash([fn.__dict__, fn.__code__])
  """

  if type(o) == DictProxyType:
    o2 = {}
    for k, v in o.items():
      if not k.startswith("__"):
        o2[k] = v
    o = o2  

  if isinstance(o, (set, tuple, list)):

    return tuple([make_hash(e) for e in o])    

  elif not isinstance(o, dict):

    return hash(o)

  new_o = copy.deepcopy(o)
  for k, v in new_o.items():
    new_o[k] = make_hash(v)

  return hash(tuple(frozenset(sorted(new_o.items()))))

您可以使用它来返回您想要的许多元素的哈希元组:

# -7666086133114527897
print (make_hash(func.__code__))

# (-7666086133114527897, 3527539)
print (make_hash([func.__code__, func.__dict__]))

# (-7666086133114527897, 3527539, -509551383349783210)
print (make_hash([func.__code__, func.__dict__, func.__name__]))

注意:以上所有代码都假定为Python 3.x.没有在早期版本中测试,虽然我认为make_hash()可以在2.7.2中使用。至于使示例有效,我确实知道

func.__code__ 

应该换成

func.func_code

11
投票

这是一个更清晰的解决方案。

def freeze(o):
  if isinstance(o,dict):
    return frozenset({ k:freeze(v) for k,v in o.items()}.items())

  if isinstance(o,list):
    return tuple([freeze(v) for v in o])

  return o


def make_hash(o):
    """
    makes a hash out of anything that contains only list,dict and hashable types including string and numeric types
    """
    return hash(freeze(o))  

6
投票

下面的代码避免使用Python hash()函数,因为它不会提供在重新启动Python时保持一致的散列(请参阅hash function in Python 3.3 returns different results between sessions)。 make_hashable()会将对象转换为嵌套元组,make_hash_sha256()也会将repr()转换为base64编码的SHA256哈希。

import hashlib
import base64

def make_hash_sha256(o):
    hasher = hashlib.sha256()
    hasher.update(repr(make_hashable(o)).encode())
    return base64.b64encode(hasher.digest()).decode()

def make_hashable(o):
    if isinstance(o, (tuple, list)):
        return tuple((make_hashable(e) for e in o))

    if isinstance(o, dict):
        return tuple(sorted((k,make_hashable(v)) for k,v in o.items()))

    if isinstance(o, (set, frozenset)):
        return tuple(sorted(make_hashable(e) for e in o))

    return o

o = dict(x=1,b=2,c=[3,4,5],d={6,7})
print(make_hashable(o))
# (('b', 2), ('c', (3, 4, 5)), ('d', (6, 7)), ('x', 1))

print(make_hash_sha256(o))
# fyt/gK6D24H9Ugexw+g3lbqnKZ0JAcgtNW+rXIDeU2Y=

5
投票

自2013年更新回复...

以上所有答案对我来说都不可靠。原因是使用items()。据我所知,这是以机器相关的顺序出现的。

相反怎么样?

import hashlib

def dict_hash(the_dict, *ignore):
    if ignore:  # Sometimes you don't care about some items
        interesting = the_dict.copy()
        for item in ignore:
            if item in interesting:
                interesting.pop(item)
        the_dict = interesting
    result = hashlib.sha1(
        '%s' % sorted(the_dict.items())
    ).hexdigest()
    return result

4
投票

为了保留密钥顺序,而不是hash(str(dictionary))hash(json.dumps(dictionary)),我更喜欢快速和肮脏的解决方案:

from pprint import pformat
h = hash(pformat(dictionary))

它甚至可以用于像DateTime这样的类型,而不是JSON可序列化的类型。


3
投票

你可以使用第三方frozendict module冻结你的字典并使其可以清洗。

from frozendict import frozendict
my_dict = frozendict(my_dict)

要处理嵌套对象,您可以使用:

import collections.abc

def make_hashable(x):
    if isinstance(x, collections.abc.Hashable):
        return x
    elif isinstance(x, collections.abc.Sequence):
        return tuple(make_hashable(xi) for xi in x)
    elif isinstance(x, collections.abc.Set):
        return frozenset(make_hashable(xi) for xi in x)
    elif isinstance(x, collections.abc.Mapping):
        return frozendict({k: make_hashable(v) for k, v in x.items()})
    else:
        raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))

如果要支持更多类型,请使用functools.singledispatch(Python 3.7):

@functools.singledispatch
def make_hashable(x):
    raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))

@make_hashable.register
def _(x: collections.abc.Hashable):
    return x

@make_hashable.register
def _(x: collections.abc.Sequence):
    return tuple(make_hashable(xi) for xi in x)

@make_hashable.register
def _(x: collections.abc.Set):
    return frozenset(make_hashable(xi) for xi in x)

@make_hashable.register
def _(x: collections.abc.Mapping):
    return frozendict({k: make_hashable(v) for k, v in x.items()})

# add your own types here

0
投票

一般方法很好,但您可能需要考虑散列方法。

SHA是为加密强度而设计的(速度也是如此,但强度更重要)。您可能想要考虑到这一点。因此,使用内置的hash函数可能是一个好主意,除非安全性在某种程度上是关键。

© www.soinside.com 2019 - 2024. All rights reserved.