为了缓存目的,我需要从字典中存在的GET参数生成一个缓存键。

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


当前回答

我是这样做的:

hash(str(my_dict))

其他回答

更新自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

下面的代码避免使用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=

虽然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()))

一点:xor可以工作,部分原因是dict保证键是唯一的。sum可以工作,因为Python会按位截断结果。

如果你想散列一个多集,sum是更可取的。对于xor, {a}将哈希到与{a, a, a}相同的值,因为x ^ x ^ x = x。

如果您确实需要SHA提供的保证,那么这并不适合您。但是在集合中使用字典,这将很好;Python容器对某些冲突具有弹性,底层哈希函数非常好。

我是这样做的:

hash(str(my_dict))

您可以使用地图库来做到这一点。具体来说,地图。FrozenMap

import maps
fm = maps.FrozenMap(my_dict)
hash(fm)

要安装地图,只需执行:

pip install maps

它也处理嵌套的dict大小写:

import maps
fm = maps.FrozenMap.recurse(my_dict)
hash(fm)

免责声明:我是地图库的作者。