EDIT : Se tutte le tue chiavi sono stringhe , prima di continuare a leggere questa risposta, vedi la soluzione significativamente più semplice (e più veloce) di Jack O'Connor (che funziona anche con i dizionari annidati).
Sebbene sia stata accettata una risposta, il titolo della domanda è "Hashing a Python Dictionary" e la risposta è incompleta per quanto riguarda quel titolo. (Per quanto riguarda il corpo della domanda, la risposta è completa.)
Dizionari nidificati
Se si cerca Stack Overflow su come eseguire l'hashing di un dizionario, si potrebbe inciampare su questa domanda appropriatamente intitolata e rimanere insoddisfatti se si sta tentando di eseguire l'hashing di moltiplicare i dizionari nidificati. La risposta sopra non funzionerà in questo caso e dovrai implementare una sorta di meccanismo ricorsivo per recuperare l'hash.
Ecco uno di questi meccanismi:
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()))))
Bonus: hash di oggetti e classi
La hash()
funzione funziona benissimo quando si eseguono hash di classi o istanze. Tuttavia, ecco un problema che ho riscontrato con l'hash, per quanto riguarda gli oggetti:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
L'hash è lo stesso, anche dopo che ho modificato foo. Questo perché l'identità di foo non è cambiata, quindi l'hash è lo stesso. Se vuoi che foo abbia l'hash in modo diverso a seconda della sua definizione attuale, la soluzione è quella di eseguire l'hash di tutto ciò che sta realmente cambiando. In questo caso, l' __dict__
attributo:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Ahimè, quando provi a fare la stessa cosa con la classe stessa:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
La __dict__
proprietà class non è un dizionario normale:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Ecco un meccanismo simile al precedente che gestirà le classi in modo appropriato:
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()))))
Puoi usarlo per restituire una tupla di hash di quanti elementi desideri:
# -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: tutto il codice sopra riportato presuppone Python 3.x. Non ho testato nelle versioni precedenti, anche se presumo make_hash()
funzionerà, diciamo, 2.7.2. Per quanto riguarda rendendo il lavoro esempi, io non so che
func.__code__
dovrebbe essere sostituito con
func.func_code