出于缓存目的,我需要根据字典中存在的GET参数生成缓存键。
目前,我正在使用sha1(repr(sorted(my_dict.items())))
(sha1()
一种内部使用hashlib的便捷方法),但我很好奇是否有更好的方法。
{'a': 1, 'b':2}
在语义上是相同的{'b':2, 'a':1}
)。我还没有用在任何过于复杂的事情上,所以YMMV但欢迎反馈。
出于缓存目的,我需要根据字典中存在的GET参数生成缓存键。
目前,我正在使用sha1(repr(sorted(my_dict.items())))
(sha1()
一种内部使用hashlib的便捷方法),但我很好奇是否有更好的方法。
{'a': 1, 'b':2}
在语义上是相同的{'b':2, 'a':1}
)。我还没有用在任何过于复杂的事情上,所以YMMV但欢迎反馈。
Answers:
如果您的字典未嵌套,则可以使用字典的项目进行冻结设置,然后使用hash()
:
hash(frozenset(my_dict.items()))
与生成JSON字符串或字典表示相比,此方法的计算强度要低得多。
更新:请参阅下面的注释,为什么这种方法可能无法产生稳定的结果。
hash()
函数不能产生稳定的输出可能会很有趣。这意味着,在给定相同输入的情况下,对于同一个python解释器的不同实例,它将返回不同的结果。在我看来,每次启动解释器时都会生成某种种子值。
sorted(d.items())
仅仅使用它不足以使我们保持稳定。其中的某些值d
也可以是字典,并且它们的键仍将以任意顺序出现。只要所有键都是字符串,我更喜欢使用:
json.dumps(d, sort_keys=True)
就是说,如果散列需要在不同的机器或Python版本之间保持稳定,我不确定这是防弹的。您可能想要添加separators
和ensure_ascii
参数,以保护自己免受在那里对默认值的任何更改。我将不胜感激。
ensure_ascii
论点可以防止这种完全假设的问题。
make_hash
。gist.github.com/charlax/b8731de51d2ea86c6eb9
default=str
到dumps
命令中来克服这一点。似乎工作得很好。
编辑:如果您所有的键都是字符串,那么在继续阅读此答案之前,请参阅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
las,当您尝试对类本身执行相同的操作时:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
class __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__]))
注意:以上所有代码均假定使用Python3.x。尽管我认为make_hash()
可以在2.7.2中使用,但并未在早期版本中进行测试。至于使这些示例可行,我确实知道
func.__code__
应该替换为
func.func_code
hash
周围的列表和元组添加了呼叫。否则,它将获取我的恰好是字典中值的整数列表,然后返回哈希表,这不是我想要的。
这是一个更清晰的解决方案。
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)):
则此功能可以在任何对象上使用。
下面的代码避免使用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=
make_hash_sha256(((0,1),(2,3)))==make_hash_sha256({0:1,2:3})==make_hash_sha256({2:3,0:1})!=make_hash_sha256(((2,3),(0,1)))
。这不是我要找的解决方案,但这是一个不错的中间。我正在考虑在type(o).__name__
每个元组的开头添加以强制区分。
tuple(sorted((make_hashable(e) for e in o)))
从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
照顾了这一点
hash
并不关心如何打印frozenset内容或类似内容。在多台机器和python版本中对其进行测试,您将看到。
保留键顺序,而不是 hash(str(dictionary))
或者hash(json.dumps(dictionary))
我宁愿快速和肮脏的解决方案:
from pprint import pformat
h = hash(pformat(dictionary))
即使对于像DateTime
JSON不可序列化的类型之类的类型,它也将起作用。
您可以使用第三方frozendict
模块冻结字典并使其可哈希化。
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
dict
的DataFrame
对象。
elif
子句以使其与DataFrame
s一起使用: elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
我将编辑答案,看看是否接受...
hash
随机化是python 3.7中默认启用的蓄意安全功能。
您可以使用地图库来做到这一点。具体来说就是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
图书馆的作者。
.recurse
。请参阅maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurse。列表中的排序在语义上是有意义的,如果您希望顺序独立,则可以在调用之前将列表转换为集合.recurse
。您还可以使用该list_fn
参数来.recurse
使用与tuple
(frozenset
我这样做是这样的:
hash(str(my_dict))
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
(尽管它可能适用于某些词典,但不能保证对所有词典都适用)。