EDITAR : Si todas sus claves son cadenas , entonces antes de continuar leyendo esta respuesta, consulte la solución significativamente más simple (y más rápida) de Jack O'Connor (que también funciona para los diccionarios anidados hash).
Aunque se ha aceptado una respuesta, el título de la pregunta es "Hashing a python dictionary", y la respuesta es incompleta con respecto a ese título. (Con respecto al cuerpo de la pregunta, la respuesta está completa).
Diccionarios anidados
Si uno busca en Stack Overflow cómo hash un diccionario, uno puede tropezar con esta pregunta acertadamente titulada, y dejar insatisfecho si está intentando hash multiplicar diccionarios anidados. La respuesta anterior no funcionará en este caso, y tendrá que implementar algún tipo de mecanismo recursivo para recuperar el hash.
Aquí hay uno de esos 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()))))
Bonus: objetos y clases hash
La hash()
función funciona muy bien cuando hash clases o instancias. Sin embargo, aquí hay un problema que encontré con hash, en lo que respecta a los objetos:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
El hash es el mismo, incluso después de haber modificado foo. Esto se debe a que la identidad de foo no ha cambiado, por lo que el hash es el mismo. Si desea que el hash haga hash de manera diferente dependiendo de su definición actual, la solución es eliminar lo que realmente está cambiando. En este caso, el __dict__
atributo:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Por desgracia, cuando intentas hacer lo mismo con la clase misma:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
La __dict__
propiedad de clase no es un diccionario normal:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Aquí hay un mecanismo similar al anterior que manejará las clases adecuadamente:
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()))))
Puede usar esto para devolver una tupla hash de la cantidad de elementos que desee:
# -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 el código anterior supone Python 3.x. No probé en versiones anteriores, aunque supongo make_hash()
que funcionará en, digamos, 2.7.2. En lo que a hacer el trabajo de ejemplos, que no sé que
func.__code__
debe ser reemplazado con
func.func_code