EDITAR : Si toutes vos clés sont des chaînes de caractères Alors, avant de poursuivre la lecture de cette réponse, veuillez consulter l'article de Jack O'Connor sur le sujet. une solution plus simple (et plus rapide) (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 cherche sur Stack Overflow comment hacher un dictionnaire, on risque de tomber sur cette question bien intitulée, et de ne pas être satisfait si l'on tente de hacher des dictionnaires imbriqués plusieurs fois. 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 l'un de ces mécanismes :
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 : Hachage d'objets et de classes
El hash()
fonctionne très bien lorsque vous hachurez des classes ou des instances. Cependant, voici un problème que j'ai trouvé avec le hachage, en ce qui concerne les objets :
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Le hachage est le même, même après que j'ai 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 soit haché différemment en fonction de sa définition actuelle, la solution est de hacher ce qui change réellement. Dans ce cas, le __dict__
attribut :
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Hélas, lorsque vous tentez de faire la même chose avec la classe elle-même :
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
La classe __dict__
n'est pas un dictionnaire normal :
print (type(Foo.__dict__)) # type <'dict_proxy'>
Voici un mécanisme similaire au précédent qui traitera les classes de manière appropriée :
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()))))
Vous pouvez l'utiliser pour renvoyer un tuple de hachage contenant autant d'éléments que vous le souhaitez :
# -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__]))
NOTE : tout le code ci-dessus suppose Python 3.x. Je n'ai pas testé les versions antérieures, bien que je suppose que make_hash()
fonctionnera dans, disons, 2.7.2. Pour ce qui est de faire fonctionner les exemples, je faire sachez que
func.__code__
doit être remplacé par
func.func_code
7 votes
La solution la plus courte est d'utiliser json.dumps(my_dict, sort_keys=True) à la place, ce qui permettra d'effectuer une récursion dans les valeurs du dict.
3 votes
Pour info, concernant les décharges, stackoverflow.com/a/12739361/1082367 dit "La sortie de pickle n'est pas garantie d'être canonique pour des raisons similaires à celles de dict et set order qui sont non-déterministes. N'utilisez pas pickle, pprint ou repr pour le hachage".
0 votes
Trier les clés du dict, pas les éléments, j'enverrais également les clés à la fonction de hachage.
2 votes
Une histoire intéressante sur le hachage des structures de données mutables (comme les dictionnaires) : python.org/dev/peps/pep-0351 a été proposé pour permettre de geler arbitrairement des objets, mais rejeté. Pour le raisonnement, voir ce fil de discussion dans python-dev : mail.python.org/pipermail/python-dev/2006-February/060793.html
1 votes
Si vos données sont au format json, et que vous souhaitez un hachage sémantiquement invariant, vérifiez github.com/schollii/sandals/blob/master/json_sem_hash.py . Il fonctionne sur des structures imbriquées (bien sûr, depuis json), et ne dépend pas des internes de dict comme l'ordre préservé (qui a évolué au cours de la vie de python), et donnera le même hachage si deux structures de données sont sémantiquement les mêmes (comme
{'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.0 votes
Ne fonctionne pas pour moi avec
d={'a': 'a', 'b': 'b'}; hashlib.md5(frozenset(d.items()))
donne une erreurTypeError: object supporting the buffer API required
1 votes
@shelper vous avez oublié le
repr()
(et éventuellement un.encode()
en python 3)