Hashing a dictionary?
为了缓存的目的,我需要从dict中存在的get参数生成一个缓存键。
目前我正在使用
使用
1 | json.dumps(d, sort_keys=True) |
也就是说,如果散列需要在不同的机器或Python版本中保持稳定,我不确定这是否是防弹的。您可能需要添加
如果您的字典没有嵌套,您可以使用dict的项创建一个frozenset,并使用
1 | hash(frozenset(my_dict.items())) |
这比生成JSON字符串或字典的表示要少得多的计算量。
编辑:如果您的所有键都是字符串,那么在继续阅读此答案之前,请参阅Jack O'Connor的明显更简单(更快)的解决方案(也适用于哈希嵌套字典)。
虽然已经接受了一个答案,但问题的标题是"hashing a python dictionary",关于这个标题,答案是不完整的。(关于问题主体,答案是完整的。)
嵌套词典
如果搜索堆栈溢出以查找如何散列字典,可能会偶然发现这个标题合适的问题,如果试图散列多个嵌套字典,可能会留下不满意的结果。在这种情况下,上面的答案不起作用,您必须实现某种递归机制来检索散列。
这是一个这样的机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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()函数在散列类或实例时非常有用。但是,我发现了一个关于对象的哈希问题:
1 2 3 4 5 | class Foo(object): pass foo = Foo() print (hash(foo)) # 1209812346789 foo.a = 1 print (hash(foo)) # 1209812346789 |
散列是一样的,即使在我改变了foo之后。这是因为foo的标识没有更改,所以散列是相同的。如果您希望foo根据其当前定义进行不同的散列,那么解决方案是散列实际更改的内容。在这种情况下,"听写"属性:
1 2 3 4 5 | class Foo(object): pass foo = Foo() print (make_hash(foo.__dict__)) # 1209812346789 foo.a = 1 print (make_hash(foo.__dict__)) # -78956430974785 |
唉,当你试图对类本身做同样的事情时:
1 | print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy' |
class_uu dict_uuuu属性不是普通字典:
1 | print (type(Foo.__dict__)) # type <'dict_proxy'> |
这里有一个与前面类似的机制,可以适当地处理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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())))) |
您可以使用它返回一个哈希元组,其中包含您想要的任意多个元素:
1 2 3 4 5 6 7 8 | # -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中工作。至于让这些例子起作用,我知道
1 | func.__code__ |
应替换为
1 | func.func_code |
这里有一个更清晰的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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)) |
下面的代码避免使用python hash()函数,因为它不会在python重新启动时提供一致的哈希(请参见python 3.3中的hash函数在会话之间返回不同的结果)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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= |
2013年更新回复…
以上的答案对我来说都不可靠。原因是使用了items()。据我所知,这是一个依赖于机器的顺序。
改成这个怎么样?
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
为了保留关键订单,我希望快速而肮脏的解决方案,而不是
1 2 | from pprint import pformat h = hash(pformat(dictionary)) |
它甚至适用于
您可以使用第三方
1 2 | from frozendict import frozendict my_dict = frozendict(my_dict) |
对于处理嵌套对象,可以使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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__)) |
如果要支持更多类型,请使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @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 |
您可以使用地图库来执行此操作。具体来说,maps.frozenmap
1 2 3 | import maps fm = maps.FrozenMap(my_dict) hash(fm) |
要安装
1 | pip install maps |
它还处理嵌套的
1 2 3 | import maps fm = maps.FrozenMap.recurse(my_dict) hash(fm) |
免责声明:我是
一般的方法很好,但是您可能需要考虑哈希方法。
sha是为加密强度(速度也是,但强度更重要)而设计的。您可能需要考虑到这一点。因此,使用内置的
我是这样做的:
1 | hash(str(my_dict)) |