J'essaie de comprendre la hash
fonction Python sous le capot. J'ai créé une classe personnalisée où toutes les instances renvoient la même valeur de hachage.
class C:
def __hash__(self):
return 42
J'ai juste supposé qu'une seule instance de la classe ci-dessus peut être dans un dict
à tout moment, mais en fait, a dict
peut avoir plusieurs éléments avec le même hachage.
c, d = C(), C()
x = {c: 'c', d: 'd'}
print(x)
# {<__main__.C object at 0x7f0824087b80>: 'c', <__main__.C object at 0x7f0823ae2d60>: 'd'}
# note that the dict has 2 elements
J'ai expérimenté un peu plus et j'ai trouvé que si je remplace la __eq__
méthode de telle sorte que toutes les instances de la classe se comparent égales, alors la dict
seule n'autorise qu'une seule instance.
class D:
def __hash__(self):
return 42
def __eq__(self, other):
return True
p, q = D(), D()
y = {p: 'p', q: 'q'}
print(y)
# {<__main__.D object at 0x7f0823a9af40>: 'q'}
# note that the dict only has 1 element
Je suis donc curieux de savoir comment un dict
peut avoir plusieurs éléments avec le même hachage.
Réponses:
Pour une description détaillée du fonctionnement du hachage de Python, consultez ma réponse à Pourquoi le retour anticipé est-il plus lent qu'autre chose?
Fondamentalement, il utilise le hachage pour choisir un emplacement dans la table. S'il y a une valeur dans l'emplacement et que le hachage correspond, il compare les éléments pour voir s'ils sont égaux.
Si le hachage ne correspond pas ou si les éléments ne sont pas égaux, il essaie un autre emplacement. Il existe une formule pour choisir ceci (que je décris dans la réponse référencée), et elle extrait progressivement les parties inutilisées de la valeur de hachage; mais une fois qu'il les a tous utilisés, il finira par se frayer un chemin à travers tous les emplacements de la table de hachage. Cela garantit finalement que nous trouvons un élément correspondant ou un emplacement vide. Lorsque la recherche trouve un emplacement vide, elle insère la valeur ou abandonne (selon que nous ajoutons ou obtenons une valeur).
La chose importante à noter est qu'il n'y a pas de listes ou de buckets: il y a juste une table de hachage avec un nombre particulier d'emplacements, et chaque hachage est utilisé pour générer une séquence d'emplacements candidats.
la source
Voici tout sur les dictionnaires Python que j'ai pu rassembler (probablement plus que quiconque voudrait savoir; mais la réponse est complète). Un cri à Duncan pour avoir signalé que les dictionnaires Python utilisent des créneaux horaires et m'ont conduit dans ce terrier de lapin.
O(1)
recherche par index).La figure ci-dessous est une représentation logique d'une table de hachage python. Dans la figure ci-dessous, 0, 1, ..., i, ... sur la gauche sont les indices des slots dans la table de hachage (ils sont juste à des fins d'illustration et ne sont pas stockés avec la table évidemment!).
# Logical model of Python Hash table -+-----------------+ 0| <hash|key|value>| -+-----------------+ 1| ... | -+-----------------+ .| ... | -+-----------------+ i| ... | -+-----------------+ .| ... | -+-----------------+ n| ... | -+-----------------+
Lorsqu'un nouveau dict est initialisé, il commence avec 8 emplacements . (voir dictobject.h: 49 )
i
basé sur le hachage de la clé. CPython utilise initiali = hash(key) & mask
. Oùmask = PyDictMINSIZE - 1
, mais ce n'est pas vraiment important). Notez simplement que l'emplacement initial, i, qui est vérifié dépend du hachage de la clé.<hash|key|value>
). Mais que faire si cet emplacement est occupé!? Très probablement parce qu'une autre entrée a le même hachage (collision de hachage!)==
comparaison, pas lais
comparaison) de l'entrée dans l'emplacement avec la clé de l'entrée actuelle à insérer ( dictobject.c: 337 , 344-345 ). Si les deux correspondent, alors il pense que l'entrée existe déjà, abandonne et passe à l'entrée suivante à insérer. Si le hachage ou la clé ne correspondent pas, il commence à sonder .Voilà! L'implémentation Python de dict vérifie à la fois l'égalité de hachage de deux clés et l'égalité normale (
==
) des clés lors de l'insertion d'éléments. Donc, en résumé, s'il y a deux clés,a
etb
ethash(a)==hash(b)
, maisa!=b
, alors les deux peuvent exister harmonieusement dans un dict Python. Mais sihash(a)==hash(b)
eta==b
, alors ils ne peuvent pas être tous les deux dans le même dict.Parce que nous devons sonder après chaque collision de hachage, un effet secondaire d'un trop grand nombre de collisions de hachage est que les recherches et les insertions deviendront très lentes (comme le souligne Duncan dans les commentaires ).
Je suppose que la réponse courte à ma question est: "Parce que c'est ainsi qu'il est implémenté dans le code source;)"
Bien que cela soit bon à savoir (pour les points de geek?), Je ne sais pas comment cela peut être utilisé dans la vraie vie. Parce qu'à moins que vous n'essayiez de casser explicitement quelque chose, pourquoi deux objets qui ne sont pas égaux auraient-ils le même hachage?
la source
Edit : la réponse ci-dessous est l'un des moyens possibles de gérer les collisions de hachage, ce n'est cependant pas ainsi que Python le fait. Le wiki de Python référencé ci-dessous est également incorrect. La meilleure source donnée par @Duncan ci-dessous est l'implémentation elle-même: https://github.com/python/cpython/blob/master/Objects/dictobject.c Je m'excuse pour la confusion.
Il stocke une liste (ou un seau) d'éléments au niveau du hachage, puis parcourt cette liste jusqu'à ce qu'il trouve la clé réelle dans cette liste. Une image en dit plus que mille mots:
Ici, vous voyez
John Smith
et lesSandra Dee
deux hachage152
. Bucket les152
contient tous les deux. Lors de la recherche,Sandra Dee
il trouve d'abord la liste dans le seau152
, puis parcourt cette liste jusqu'à ce qu'ilSandra Dee
soit trouvé et retourne521-6955
.Ce qui suit est faux, c'est seulement ici pour le contexte: Sur le wiki de Python, vous pouvez trouver du code (pseudo?) Comment Python effectue la recherche.
Il existe en fait plusieurs solutions possibles à ce problème, consultez l'article wikipedia pour un bel aperçu: http://en.wikipedia.org/wiki/Hash_table#Collision_resolution
la source
Les tables de hachage, en général, doivent permettre les collisions de hachage! Vous aurez de la malchance et deux choses finiront par devenir la même chose. En dessous, il y a un ensemble d'objets dans une liste d'éléments qui ont la même clé de hachage. Habituellement, il n'y a qu'une seule chose dans cette liste, mais dans ce cas, elle continuera à les empiler dans la même liste. La seule façon dont il sait qu'ils sont différents est d'utiliser l'opérateur égal.
Lorsque cela se produit, vos performances se dégraderont avec le temps, c'est pourquoi vous voulez que votre fonction de hachage soit aussi "aléatoire que possible".
la source
Dans le fil de discussion, je n'ai pas vu ce que fait exactement python avec les instances d'une classe définie par l'utilisateur lorsque nous le mettons dans un dictionnaire en tant que clés. Lisons une documentation: elle déclare que seuls les objets hachables peuvent être utilisés comme clés. Les hashable sont toutes les classes intégrées immuables et toutes les classes définies par l'utilisateur.
Donc, si vous avez un __hash__ constamment dans votre classe, mais ne fournissant aucune méthode __cmp__ ou __eq__, alors toutes vos instances sont inégales pour le dictionnaire. D'un autre côté, si vous fournissez une méthode __cmp__ ou __eq__, mais pas __hash__, vos instances sont toujours inégales en termes de dictionnaire.
class A(object): def __hash__(self): return 42 class B(object): def __eq__(self, other): return True class C(A, B): pass dict_a = {A(): 1, A(): 2, A(): 3} dict_b = {B(): 1, B(): 2, B(): 3} dict_c = {C(): 1, C(): 2, C(): 3} print(dict_a) print(dict_b) print(dict_c)
Production
{<__main__.A object at 0x7f9672f04850>: 1, <__main__.A object at 0x7f9672f04910>: 3, <__main__.A object at 0x7f9672f048d0>: 2} {<__main__.B object at 0x7f9672f04990>: 2, <__main__.B object at 0x7f9672f04950>: 1, <__main__.B object at 0x7f9672f049d0>: 3} {<__main__.C object at 0x7f9672f04a10>: 3}
la source