哈希字典?

时间:2011-05-04 13:19:39

标签: python hash dictionary

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

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

15 个答案:

答案 0 :(得分:105)

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

json.dumps(d, sort_keys=True)

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

答案 1 :(得分:92)

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

hash(frozenset(my_dict.items()))

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

答案 2 :(得分:56)

编辑:如果您的所有密钥都是字符串,那么在继续阅读此答案之前,请参阅Jack O'Connor的重要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

答案 3 :(得分: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))  

答案 4 :(得分: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 :(得分: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

答案 6 :(得分:4)

要保留关键订单,而不是hash(str(dictionary))hash(json.dumps(dictionary))我更喜欢快速而肮脏的解决方案:

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

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

答案 7 :(得分:4)

虽然 hash(frozenset(x.items())hash(tuple(sorted(x.items())) 可以工作,但它在分配和复制所有键值对方面做了大量工作。哈希函数确实应该避免大量内存分配。

一点点数学知识可以帮上忙。大多数散列函数的问题在于它们假设顺序很重要。要散列无序结构,您需要一个交换操作。乘法效果不佳,因为任何散列到 0 的元素都意味着整个乘积为 0。按位 &| 倾向于全 0 或 1。有两个很好的候选者:addition 和 xor。

from functools import reduce
from operator import xor

class hashable(dict):
    def __hash__(self):
        return reduce(xor, map(hash, self.items()), 0)

    # Alternative
    def __hash__(self):
        return sum(map(hash, self.items()))

一点:xor 起作用,部分是因为 dict 保证键是唯一的。 sum 之所以有效,是因为 Python 会按位截断结果。

如果你想对一个多集进行散列,最好使用 sum。使用异或,{a} 将散列到与 {a, a, a} 相同的值,因为 x ^ x ^ x = x

如果您确实需要 SHA 做出的保证,这对您不起作用。但是要在集合中使用字典,这会很好用; Python 容器对一些冲突有弹性,底层的哈希函数非常好。

答案 8 :(得分:2)

您可以使用第三方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

答案 9 :(得分:1)

解决该问题的一种方法是将字典中的项目做成元组:

hash(tuple(my_dict.items()))

答案 10 :(得分:1)

此线程中投票率最高的答案对我而言不起作用,因为PYTHOPYTHONHASHSEED导致他们的哈希函数在不同的计算机上给出不同的结果。

我调整了该线程的所有提示,并提出了一个适用于我的解决方案。

import collections
import hashlib
import json


def simplify_object(o):
    if isinstance(o, dict):
        ordered_dict = collections.OrderedDict(sorted(o.items()))
        for k, v in ordered_dict.items():
            v = simplify_object(v)
            ordered_dict[str(k)] = v
        o = ordered_dict
    elif isinstance(o, (list, tuple, set)):
        o = [simplify_object(el) for el in o]
    else:
        o = str(o).strip()
    return o


def make_hash(o):
    o = simplify_object(o)
    bytes_val = json.dumps(o, sort_keys=True, ensure_ascii=True, default=str)
    hash_val = hashlib.sha1(bytes_val.encode()).hexdigest()
    return hash_val

答案 11 :(得分:1)

MD5 哈希

对我来说产生最稳定结果的方法是使用 md5 哈希和 json.stringify

from typing import Dict, Any
import hashlib
import json

def dict_hash(dictionary: Dict[str, Any]) -> str:
    """MD5 hash of a dictionary."""
    dhash = hashlib.md5()
    # We need to sort arguments so {'a': 1, 'b': 2} is
    # the same as {'b': 2, 'a': 1}
    encoded = json.dumps(dictionary, sort_keys=True).encode()
    dhash.update(encoded)
    return dhash.hexdigest()

答案 12 :(得分:0)

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

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

答案 13 :(得分:0)

您可以使用enter image description here库来执行此操作。具体来说,maps

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库的作者。

答案 14 :(得分:-7)

我这样做:

hash(str(my_dict))