为了缓存目的,我需要从一个包含在字典中的GET参数生成一个缓存键。
目前,我正在使用sha1(repr(sorted(my_dict.items())))
(sha1()
是一个使用hashlib内部的便捷方法),但我很好��是否有更好的方法。
为了缓存目的,我需要从一个包含在字典中的GET参数生成一个缓存键。
目前,我正在使用sha1(repr(sorted(my_dict.items())))
(sha1()
是一个使用hashlib内部的便捷方法),但我很好��是否有更好的方法。
使用 sorted(d.items())
并不能保证我们得到一个稳定的表示。因为在字典 d
中有些值可能也是字典,它们的键仍将以任意顺序出现。只要所有键都是字符串,我更喜欢使用:
json.dumps(d, sort_keys=True)
话虽如此,如果哈希需要在不同的计算机或Python版本之间稳定,我不能确定这是100%可靠的。您可能需要添加separators
和ensure_ascii
参数以保护自己免受默认设置的任何更改。 我会很感激您的评论。
ensure_ascii
参数可以完全保护您免受这个完全假设的问题的影响。 - Jack O'Connormake_hash
快得多。https://gist.github.com/charlax/b8731de51d2ea86c6eb9 - charlaxdumps
命令中添加default=str
来解决这个问题。看起来效果不错。 - mlissner如果您的字典没有嵌套,您可以使用字典的项目创建一个不可变集合,并使用hash()
函数:
hash(frozenset(my_dict.items()))
这比生成JSON字符串或字典的表示方式要少得多的计算量。
更新:请参见下面的评论,说明此方法可能不会产生稳定的结果。
hash()
函数不会产生稳定的输出,这可能很有趣。这意味着,给定相同的输入,在同一Python解释器的不同实例中它会返回不同的结果。对我来说,这看起来像是每次启动解释器时生成了某种种子值。 - Hermann Schachner编辑: 如果 所有的键都是字符串,那么在继续阅读本答案之前,请查看Jack O'Connor的更简单(更快)的解决方案(也适用于哈希嵌套字典)。
尽管有一个答案被接受了,但问题的标题是“哈希Python字典”,而就该标题而言,答案并不完整。(针对问题正文,答案是完整的。)
嵌套字典
如果你在 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
hash(json.dumps(d, sort_keys=True))
差了18倍。证明链接:https://pastebin.com/XDZGhNHs - kolypto以下代码避免使用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=
tuple(sorted((make_hashable(e) for e in o)))
- Suraj对于我来说最稳定的方法是使用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()
yaml
替换json
有所帮助,因为它可以序列化类及其属性。import yaml
...
yaml.dump(dictionary, sort_keys=True).encode()
- DomDevjson.dumps
看起来可以稳定地转储嵌套字典。 - dfrankow这里是一个更清晰的解决方案。
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))
if isinstance(o,list):
改为 if isinstance(obj, (set, tuple, list)):
,那么这个函数就可以在任何对象上运行。 - Peter Schornhash(frozenset(x.items())
和 hash(tuple(sorted(x.items()))
能够工作,但是这样做会分配大量内存并复制所有的键值对。一个好的哈希函数应该避免大量的内存分配。&
和 |
倾向于全部为 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()))
{a}
将与{a, a, a}
哈希为同样的值,因为x ^ x ^ x = x
。from deepdiff import DeepHash
obj = {'a':'1', 'b':'2'}
hashes = DeepHash(obj)[obj]
从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
dict.items
不返回可预测排序列表有什么影响吗?frozenset
可以解决这个问题。 - glarrainhash
并不关心frozenset内容的打印方式或类似的东西。在几台机器和Python版本中进行测试,你就会看到。 - glarrainhash(str(dictionary))
或hash(json.dumps(dictionary))
。from pprint import pformat
h = hash(pformat(dictionary))
即使对于无法JSON序列化的类型(如DateTime
等),它也能正常工作。
在语义上与
{'b':2, 'a':1}`相同)。我还没有在任何太复杂的东西上使用过它,所以效果可能有所不同,但欢迎反馈。 - Oliverrepr()
函数(在 Python 3 中可能还需要加上.encode()
)。 - ThiefMaster