EDYCJA : Jeśli wszystkie twoje klucze są ciągami , to przed kontynuowaniem czytania tej odpowiedzi zapoznaj się ze znacznie prostszym (i szybszym) rozwiązaniem Jacka O'Connora (które działa również w przypadku haszowania zagnieżdżonych słowników).
Chociaż odpowiedź została zaakceptowana, tytuł pytania brzmi „Haszowanie słownika Pythona”, a odpowiedź jest niekompletna w odniesieniu do tego tytułu. (Jeśli chodzi o treść pytania, odpowiedź jest kompletna).
Zagnieżdżone słowniki
Jeśli ktoś szuka przepełnienia stosu, aby dowiedzieć się, jak haszować słownik, można natknąć się na to trafnie zatytułowane pytanie i pozostawić niezadowolony, jeśli ktoś próbuje haszować mnożenie zagnieżdżonych słowników. Powyższa odpowiedź nie zadziała w tym przypadku i będziesz musiał zaimplementować jakiś mechanizm rekurencyjny, aby pobrać hash.
Oto jeden taki mechanizm:
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: haszowanie obiektów i klas
hash()
Funkcja działa świetnie, gdy hash klas lub instancji. Jednak tutaj jest jeden problem, który znalazłem z hashem, jeśli chodzi o obiekty:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Hasz jest taki sam, nawet po zmianie foo. Dzieje się tak, ponieważ tożsamość foo się nie zmieniła, więc hash jest taki sam. Jeśli chcesz, aby foo różnie haszowało w zależności od jego aktualnej definicji, rozwiązaniem jest haszowanie tego, co faktycznie się zmienia. W tym przypadku __dict__
atrybut:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Niestety, kiedy próbujesz zrobić to samo z samą klasą:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
Właściwość class __dict__
nie jest zwykłym słownikiem:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Oto podobny mechanizm jak poprzednio, który będzie odpowiednio obsługiwał klasy:
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()))))
Możesz użyć tego, aby zwrócić krotkę mieszającą dowolną liczbę elementów:
# -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__]))
UWAGA: cały powyższy kod zakłada Python 3.x. Nie testowałem we wcześniejszych wersjach, choć zakładam, że make_hash()
będzie działać powiedzmy w 2.7.2. Jeśli chodzi o to, by przykłady działały, to wiem
func.__code__
należy zastąpić
func.func_code