哈希字典?

Thi*_*ter 135 python hash dictionary

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

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

Jac*_*nor 118

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

json.dumps(d, sort_keys=True)
Run Code Online (Sandbox Code Playgroud)

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

  • 只要所有键都是字符串,此解决方案才有效,例如json.dumps({'a':{(0,5):5,1:3}})失败. (10认同)
  • 这只是偏执狂,但JSON允许大多数字符显示在字符串中而没有任何文字转义,因此编码器可以做出关于是否转义字符或只是通过它们的一些选择.然后风险是编码器的不同版本(或未来版本)默认情况下可以进行不同的转义选择,然后您的程序将为不同环境中的相同字典计算不同的哈希值.`ensure_ascii`参数可以防止这个完全假设的问题. (5认同)
  • 我用不同的数据集测试了它的性能,它比`make_hash`快得多.https://gist.github.com/charlax/b8731de51d2ea86c6eb9 (4认同)
  • @LorenzoBelli,您可以通过在`dumps`命令中添加`default = str`来克服这个问题.似乎工作得很好. (3认同)
  • @charlax ujson不保证dict对的顺序,所以这样做是不安全的 (2认同)
  • 某些数据类型不是像datetime.datetime那样的json序列化. (2认同)
  • @mlissner然后你有两个不相等的字典生成相同的散列(一个带有整数键,一个带有字符串键) (2认同)

Imr*_*ran 99

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

hash(frozenset(my_dict.items()))
Run Code Online (Sandbox Code Playgroud)

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

  • 如果需要在不同的机器上保持一致,请注意内置哈希.在像Heroku和GAE这样的云平台上实现python将在不同的实例上返回hash()的不同值,这使得它对于必须在两个或更多"机器"之间共享的任何东西都是无用的(在heroku的情况下是dynos) (25认同)
  • 这对嵌套字典不起作用.我没有尝试过下面的解决方案(太复杂了).OP的解决方案完美无缺.我用哈希替换sha1来保存导入. (9认同)
  • @Ceaser这是行不通的,因为元组意味着排序,但是dict项目是无序的.frozenset更好. (9认同)
  • 预期.出于安全原因引入种子,据我记得添加某种内存随机化.所以你不能指望两个python进程之间的哈希是相同的 (7认同)
  • 可能有趣的是`hash()`函数不会产生稳定的输出.这意味着,在给定相同输入的情况下,它会使用相同python解释器的不同实例返回不同的结果.对我来说,每次启动解释器时都会生成某种种子值. (6认同)
  • @HermannSchachner没错-出于安全原因,Python 3.3中引入了hash()的随机种子:http://stackoverflow.com/a/27522708/196206这使该答案不可用(除非您仅在单个解释器中需要哈希稳定性)一生)。 (2认同)

jom*_*ido 59

编辑:如果你的所有键都是字符串,那么在继续阅读这个答案之前,请参阅Jack O'Connor的更简单(更快)的解决方案(这也适用于散列嵌套字典).

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

嵌套字典

如果一个人搜索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()))))
Run Code Online (Sandbox Code Playgroud)

奖励:哈希对象和类

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

class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Run Code Online (Sandbox Code Playgroud)

即使在我改变了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
Run Code Online (Sandbox Code Playgroud)

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

print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
Run Code Online (Sandbox Code Playgroud)

类__dict__属性不是普通字典:

print (type(Foo.__dict__)) # type <'dict_proxy'>
Run Code Online (Sandbox Code Playgroud)

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

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()))))
Run Code Online (Sandbox Code Playgroud)

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

# -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__]))
Run Code Online (Sandbox Code Playgroud)

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

func.__code__ 
Run Code Online (Sandbox Code Playgroud)

应该换成

func.func_code
Run Code Online (Sandbox Code Playgroud)

  • 在 Python 3.7 中,此解决方案的性能比 `hash(json.dumps(d, sort_keys=True))` 差 18 倍。证明:https://pastebin.com/XDZGhNHs (2认同)
  • @kolypto Json 解决方案取决于字符串的大小(显然),而哈希解决方案时间是稳定的。[证明](https://pastebin.com/r2e3rzPQ) 使用具有较大字符串的代码 (2认同)

小智 19

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()
Run Code Online (Sandbox Code Playgroud)


sma*_*007 12

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

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))  
Run Code Online (Sandbox Code Playgroud)

  • 如果将 `if isinstance(o,list):` 更改为 `if isinstance(obj, (set, tuple, list)):` 那么这个函数可以作用于任何对象。 (2认同)

Ben*_*Ben 11

hash(frozenset(x.items())在工作时hash(tuple(sorted(x.items())),需要做大量的分配和复制所有键值对的工作。哈希函数确实应该避免大量的内存分配。

一点数学知识在这里可以有所帮助。大多数哈希函数的问题在于它们假设顺序很重要。要散列无序结构,您需要交换运算。乘法效果不佳,因为任何元素散列为 0 意味着整个乘积为 0。按位&|倾向于全 0 或 1。有两个不错的选择:加法和异或。

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()))
Run Code Online (Sandbox Code Playgroud)

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

如果你想对多重集进行散列,那么 sum 是更好的选择。使用异或,将散列到与因为{a}相同的值。{a, a, a}x ^ x ^ x = x

如果您确实需要 SHA 做出的保证,那么这对您不起作用。但是要在集合中使用字典,这会很好地工作;Python 容器对某些冲突具有抵抗力,并且底层的哈希函数非常好。


小智 8

下面的代码避免使用Python hash()函数,因为它不会提供在Python重新启动后保持一致的哈希值(请参阅Python 3.3中的哈希函数在会话之间返回不同的结果)。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=
Run Code Online (Sandbox Code Playgroud)

  • @Suraj您不应该在散列之前对列表进行排序,因为内容顺序不同的列表绝对不是同一件事。如果项目的顺序并不重要,则问题在于您使用了错误的数据结构。您应该使用集合而不是列表。 (3认同)

小智 6

使用DeepDiff模块中的 DeepHash

from deepdiff import DeepHash
obj = {'a':'1', 'b':'2'}
hashes = DeepHash(obj)[obj]
Run Code Online (Sandbox Code Playgroud)


Ste*_*ago 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
Run Code Online (Sandbox Code Playgroud)

  • 根据定义,集合是无序的.因此,添加对象的顺序是无关紧要的.你必须意识到内置函数`hash`并不关心如何打印冻结内容或类似的内容.在几台机器和python版本中测试它,你会看到. (2认同)