问题:藏字典吗?
出于缓存目的,我需要根据字典中存在的GET参数生成缓存键。
目前,我正在使用sha1(repr(sorted(my_dict.items())))
(sha1()
一种内部使用hashlib的便捷方法),但我很好奇是否有更好的方法。
回答 0
如果您的字典未嵌套,则可以使用字典的项目进行冻结设置,然后使用hash()
:
hash(frozenset(my_dict.items()))
与生成JSON字符串或字典表示相比,此方法的计算强度要低得多。
更新:请参阅下面的注释,为什么这种方法可能无法产生稳定的结果。
回答 1
sorted(d.items())
仅仅使用它不足以使我们保持稳定。其中的某些值d
也可以是字典,并且它们的键仍将以任意顺序出现。只要所有键都是字符串,我更喜欢使用:
json.dumps(d, sort_keys=True)
就是说,如果散列需要在不同的机器或Python版本之间保持稳定,我不确定这是防弹的。您可能想要添加separators
和ensure_ascii
参数,以保护自己免受在那里对默认值的任何更改。我将不胜感激。
回答 2
编辑:如果您所有的键都是字符串,那么在继续阅读此答案之前,请参阅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
回答 3
这是一个更清晰的解决方案。
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
下面的代码避免使用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=
回答 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
保留键顺序,而不是 hash(str(dictionary))
或者hash(json.dumps(dictionary))
我宁愿快速和肮脏的解决方案:
from pprint import pformat
h = hash(pformat(dictionary))
即使对于像DateTime
JSON不可序列化的类型之类的类型,它也将起作用。
回答 7
您可以使用第三方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
回答 8
您可以使用地图库来做到这一点。具体来说就是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
图书馆的作者。
回答 9
解决该问题的一种方法是对字典中的项进行元组化:
hash(tuple(my_dict.items()))
回答 10
我这样做是这样的:
hash(str(my_dict))