EDIT : Se todas as suas chaves são strings , então antes de continuar a ler esta resposta, consulte significativamente de Jack O'Connor solução mais simples (e mais rápido) (que também trabalha para hash dicionários aninhados).
Embora uma resposta tenha sido aceita, o título da pergunta é "Hashing um dicionário python" e a resposta está incompleta em relação a esse título. (No que diz respeito ao corpo da pergunta, a resposta está completa.)
Dicionários aninhados
Se alguém pesquisar Stack Overflow para saber como fazer hash em um dicionário, poderá se deparar com essa pergunta com título adequado e ficar insatisfeito se estiver tentando hash multiplicar dicionários aninhados. A resposta acima não funcionará neste caso, e você precisará implementar algum tipo de mecanismo recursivo para recuperar o hash.
Aqui está um desses mecanismos:
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()))))
Bônus: Hashing de objetos e classes
A hash()
função funciona muito bem quando você hash classes ou instâncias. No entanto, aqui está um problema que encontrei com o hash, no que diz respeito aos objetos:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
O hash é o mesmo, mesmo depois que eu mudei de foo. Isso ocorre porque a identidade de foo não mudou, então o hash é o mesmo. Se você deseja que o hash seja diferente de acordo com sua definição atual, a solução é fazer o hash do que está realmente mudando. Nesse caso, o __dict__
atributo:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Infelizmente, quando você tenta fazer o mesmo com a própria classe:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
A __dict__
propriedade da classe não é um dicionário normal:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Aqui está um mecanismo semelhante ao anterior, que manipulará as classes adequadamente:
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()))))
Você pode usar isso para retornar uma tupla de hash de quantos elementos você desejar:
# -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__]))
NOTA: todo o código acima assume o Python 3.x. Não testou em versões anteriores, embora eu assuma make_hash()
que funcionará, digamos, 2.7.2. Tanto quanto fazer a exemplos de trabalho, eu não sei que
func.__code__
deve ser substituído por
func.func_code