Para propósitos de almacenamiento en caché, necesito generar una clave de caché a partir de argumentos GET que están presentes en un dict.
Actualmente estoy usando sha1(repr(sorted(my_dict.items())))
( sha1()
es un método de conveniencia que usa hashlib internamente) pero tengo curiosidad por saber si hay una mejor manera.
python
hash
dictionary
ThiefMaster
fuente
fuente
{'a': 1, 'b':2}
es semánticamente lo mismo que{'b':2, 'a':1}
). Todavía no lo he usado en nada demasiado complicado, así que YMMV, pero los comentarios son bienvenidos.Respuestas:
Si su diccionario no está anidado, puede hacer un congelamiento con los elementos del dict y usar
hash()
:Esto es mucho menos computacionalmente intenso que generar la cadena JSON o la representación del diccionario.
ACTUALIZACIÓN: consulte los comentarios a continuación, por qué este enfoque podría no producir un resultado estable.
fuente
hash()
función no produzca una salida estable. Esto significa que, dada la misma entrada, devuelve resultados diferentes con instancias diferentes del mismo intérprete de Python. Para mí, parece que se genera algún tipo de valor inicial cada vez que se inicia el intérprete.Usar
sorted(d.items())
no es suficiente para obtener una repr estable. Algunos de los valores end
podrían ser diccionarios también, y sus claves seguirán saliendo en un orden arbitrario. Mientras todas las teclas sean cadenas, prefiero usar:Dicho esto, si los hashes necesitan ser estables en diferentes máquinas o versiones de Python, no estoy seguro de que esto sea a prueba de balas. Es posible que desee agregar los argumentos
separators
yensure_ascii
para protegerse de cualquier cambio en los valores predeterminados allí. Agradecería los comentarios.fuente
ensure_ascii
argumento protegería contra este problema completamente hipotético.make_hash
. gist.github.com/charlax/b8731de51d2ea86c6eb9default=str
aldumps
comando. Parece funcionar bien.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:
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: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:Por desgracia, cuando intentas hacer lo mismo con la clase misma:
La
__dict__
propiedad de clase no es un diccionario normal:Aquí hay un mecanismo similar al anterior que manejará las clases adecuadamente:
Puede usar esto para devolver una tupla hash de la cantidad de elementos que desee:
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é quedebe ser reemplazado con
fuente
hash
listas y tuplas. De lo contrario, toma mis listas de enteros que resultan ser valores en mi diccionario y devuelve listas de hashes, que no es lo que quiero.Aquí hay una solución más clara.
fuente
if isinstance(o,list):
aif isinstance(obj, (set, tuple, list)):
, esta función puede funcionar en cualquier objeto.El siguiente código evita el uso de la función hash () de Python porque no proporcionará hashes que sean consistentes en los reinicios de Python (ver la función hash en Python 3.3 devuelve resultados diferentes entre sesiones ).
make_hashable()
convertirá el objeto en tuplas anidadas ymake_hash_sha256()
también lo convertirárepr()
en un hash SHA256 codificado en base64.fuente
make_hash_sha256(((0,1),(2,3)))==make_hash_sha256({0:1,2:3})==make_hash_sha256({2:3,0:1})!=make_hash_sha256(((2,3),(0,1)))
. Esta no es la solución que estoy buscando, pero es un buen intermedio. Estoy pensando en agregartype(o).__name__
al comienzo de cada una de las tuplas para forzar la diferenciación.tuple(sorted((make_hashable(e) for e in o)))
Actualizado desde 2013 respuesta ...
Ninguna de las respuestas anteriores me parece confiable. La razón es el uso de elementos (). Hasta donde yo sé, esto sale en un orden dependiente de la máquina.
¿Qué tal esto en su lugar?
fuente
dict.items
no devuelva una lista ordenada de manera predecible?frozenset
se encarga de esohash
no le importa cómo se imprimen los contenidos congelados o algo así. Pruébelo en varias máquinas y versiones de python y verá.Para preservar el orden de las claves, en lugar de
hash(str(dictionary))
ohash(json.dumps(dictionary))
preferiría una solución rápida y sucia:Funcionará incluso para tipos como
DateTime
y más que no son serializables JSON.fuente
Podrías usar el
frozendict
módulo de terceros para congelar tu dict y hacerlo hashaable.Para manejar objetos anidados, puede ir con:
Si desea admitir más tipos, use
functools.singledispatch
(Python 3.7):fuente
dict
deDataFrame
los objetos.elif
cláusula para que funcione conDataFrame
s:elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
hash
la aleatorización es una función de seguridad deliberada habilitada por defecto en python 3.7.Puede usar la biblioteca de mapas para hacer esto. Específicamente, mapas . Mapa congelado
Para instalar
maps
, solo haz:También maneja el
dict
caso anidado :Descargo de responsabilidad: soy el autor de la
maps
biblioteca.fuente
.recurse
. Ver maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurse . Ordenar en listas tiene un significado semántico, si desea independencia de orden puede convertir sus listas en conjuntos antes de llamar.recurse
. También puede usar ellist_fn
parámetro para.recurse
usar una estructura de datos hashaable diferente atuple
(.egfrozenset
)Una forma de abordar el problema es hacer una tupla de los elementos del diccionario:
fuente
Lo hago así:
fuente
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
(aunque podría funcionar para algunos diccionarios, no se garantiza que funcione en todos).