À des fins de mise en cache, je dois générer une clé de cache à partir des arguments GET qui sont présents dans un dict.
J'utilise actuellement sha1(repr(sorted(my_dict.items())))
( sha1()
c'est une méthode pratique qui utilise hashlib en interne) mais je suis curieux de savoir s'il existe un meilleur moyen.
python
hash
dictionary
ThiefMaster
la source
la source
{'a': 1, 'b':2}
est sémantiquement le même que{'b':2, 'a':1}
). Je ne l'ai pas encore utilisé sur quelque chose de trop compliqué, donc YMMV, mais les commentaires sont les bienvenus.Réponses:
Si votre dictionnaire n'est pas imbriqué, vous pouvez créer un frozenset avec les éléments du dict et utiliser
hash()
:Cela demande beaucoup moins de calculs que de générer la chaîne JSON ou la représentation du dictionnaire.
MISE À JOUR: S'il vous plaît voir les commentaires ci-dessous, pourquoi cette approche peut ne pas produire un résultat stable.
la source
hash()
fonction ne produit pas une sortie stable. Cela signifie que, étant donné la même entrée, il renvoie des résultats différents avec différentes instances du même interpréteur python. Pour moi, il semble qu'une sorte de valeur de départ soit générée à chaque démarrage de l'interpréteur.L'utilisation
sorted(d.items())
n'est pas suffisante pour nous procurer une reproduction stable. Certaines des valeurs ded
peuvent également être des dictionnaires, et leurs clés sortiront toujours dans un ordre arbitraire. Tant que toutes les clés sont des chaînes, je préfère utiliser:Cela dit, si les hachages doivent être stables sur différentes machines ou versions de Python, je ne suis pas certain que ce soit à l'épreuve des balles. Vous souhaiterez peut-être ajouter les arguments
separators
etensure_ascii
pour vous protéger de toute modification des valeurs par défaut. J'apprécierais les commentaires.la source
ensure_ascii
argument protégerait contre ce problème entièrement hypothétique.make_hash
. gist.github.com/charlax/b8731de51d2ea86c6eb9default=str
à ladumps
commande. Semble bien fonctionner.EDIT : Si toutes vos clés sont des chaînes , avant de continuer à lire cette réponse, veuillez consulter la solution nettement plus simple (et plus rapide) de Jack O'Connor (qui fonctionne également pour le hachage de dictionnaires imbriqués).
Bien qu'une réponse ait été acceptée, le titre de la question est "Hashing a python dictionary", et la réponse est incomplète en ce qui concerne ce titre. (En ce qui concerne le corps de la question, la réponse est complète.)
Dictionnaires imbriqués
Si l'on recherche Stack Overflow pour savoir comment hacher un dictionnaire, on peut tomber sur cette question bien intitulée, et ne pas être satisfait si l'on tente de hacher plusieurs dictionnaires imbriqués. La réponse ci-dessus ne fonctionnera pas dans ce cas, et vous devrez implémenter une sorte de mécanisme récursif pour récupérer le hachage.
Voici un de ces mécanismes:
Bonus: Objets et classes de hachage
La
hash()
fonction fonctionne très bien lorsque vous hachez des classes ou des instances. Cependant, voici un problème que j'ai trouvé avec le hachage, en ce qui concerne les objets:Le hachage est le même, même après avoir modifié foo. C'est parce que l'identité de foo n'a pas changé, donc le hachage est le même. Si vous voulez que foo hache différemment en fonction de sa définition actuelle, la solution est de hacher tout ce qui change réellement. Dans ce cas, l'
__dict__
attribut:Hélas, lorsque vous essayez de faire la même chose avec la classe elle-même:
La
__dict__
propriété class n'est pas un dictionnaire normal:Voici un mécanisme similaire au précédent qui gérera les classes de manière appropriée:
Vous pouvez l'utiliser pour renvoyer un tuple de hachage du nombre d'éléments que vous souhaitez:
REMARQUE: tout le code ci-dessus suppose Python 3.x. N'a pas testé dans les versions antérieures, bien que je suppose que
make_hash()
cela fonctionnera dans, disons, 2.7.2. En ce qui concerne les travaux faisant des exemples, je ne sais quedoit être remplacé par
la source
hash
listes et aux tuples autour. Sinon, il prend mes listes d'entiers qui se trouvent être des valeurs dans mon dictionnaire, et renvoie des listes de hachages, ce qui n'est pas ce que je veux.Voici une solution plus claire.
la source
if isinstance(o,list):
enif isinstance(obj, (set, tuple, list)):
alors cette fonction peut fonctionner sur n'importe quel objet.Le code ci-dessous évite d'utiliser la fonction Python hash () car elle ne fournira pas de hachages cohérents entre les redémarrages de Python (voir la fonction de hachage dans Python 3.3 renvoie des résultats différents entre les sessions ).
make_hashable()
convertira l'objet en tuples imbriqués etmake_hash_sha256()
convertira également lerepr()
en un hachage SHA256 encodé en base64.la source
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)))
. Ce n'est pas tout à fait la solution que je recherche, mais c'est un bon intermédiaire. Je pense ajoutertype(o).__name__
au début de chacun des tuples pour forcer la différenciation.tuple(sorted((make_hashable(e) for e in o)))
Mis à jour à partir de la réponse de 2013 ...
Aucune des réponses ci-dessus ne me semble fiable. La raison en est l'utilisation d'éléments (). Autant que je sache, cela sort dans un ordre dépendant de la machine.
Que diriez-vous de cela à la place?
la source
dict.items
ne pas renvoyer une liste ordonnée de manière prévisible?frozenset
s'occupe de çahash
ne se soucie pas de la façon dont le contenu du frozenset est imprimé ou quelque chose du genre. Testez-le sur plusieurs machines et versions de python et vous verrez.Pour préserver l'ordre des clés, au lieu de
hash(str(dictionary))
ouhash(json.dumps(dictionary))
je préférerais une solution rapide et sale:Cela fonctionnera même pour les types comme
DateTime
et plus qui ne sont pas sérialisables JSON.la source
Vous pouvez utiliser le
frozendict
module tiers pour geler votre dict et le rendre hachable.Pour gérer les objets imbriqués, vous pouvez utiliser:
Si vous souhaitez prendre en charge plus de types, utilisez
functools.singledispatch
(Python 3.7):la source
dict
desDataFrame
objets.elif
clause suivante pour que cela fonctionne avecDataFrame
s:elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
Je vais éditer la réponse et voir si vous l'acceptez ...hash
La randomisation est une fonctionnalité de sécurité délibérée activée par défaut dans python 3.7.Vous pouvez utiliser la bibliothèque de cartes pour ce faire. Plus précisément, les cartes.
Pour l'installer
maps
, faites simplement:Il gère également le
dict
cas imbriqué :Avertissement: Je suis l'auteur de la
maps
bibliothèque.la source
.recurse
. Voir maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurse . L'ordre dans les listes est sémantiquement significatif, si vous voulez l'indépendance de l'ordre, vous pouvez convertir vos listes en ensembles avant d'appeler.recurse
. Vous pouvez également utiliser lelist_fn
paramètre pour.recurse
utiliser une structure de données hachable différente detuple
(.egfrozenset
)Une façon d'aborder le problème consiste à créer un tuple des éléments du dictionnaire:
la source
Je fais ça comme ça:
la source
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
(bien que cela puisse fonctionner pour certains dictionnaires, il n'est pas garanti de fonctionner sur tous).