Je suis un peu confus sur ce qui peut/ne peut pas être utilisé comme clé pour un python dict.
dicked = {}
dicked[None] = 'foo' # None ok
dicked[(1,3)] = 'baz' # Tuple ok
import sys
dicked[sys] = 'bar' # wow, even a module is ok !
dicked[(1,[3])] = 'qux' # oops, not allowed
Ainsi, un tuple est un type immuable, mais si je cache une liste à l'intérieur, il ne peut pas s'agir d'une clé. Ne pourrais-je pas aussi facilement masquer une liste à l'intérieur d'un module?
J'avais une idée vague que la clé doit être "lavable" mais je vais admettre ma propre ignorance sur les détails techniques; Je ne sais pas ce qui se passe vraiment ici. Que se passerait-il si vous essayiez d'utiliser des listes comme clés, avec le hachage comme, par exemple, leur emplacement mémoire?
Il y a un bon article sur le sujet dans le Python wiki: Pourquoi les listes ne peuvent pas être des clés de dictionnaire . Comme expliqué ici:
Que se passerait-il si vous essayiez d'utiliser des listes comme clés, avec le hachage comme, par exemple, leur emplacement mémoire?
Cela peut être fait sans casser aucune des exigences, mais cela conduit à un comportement inattendu. Les listes sont généralement traitées comme si leur valeur était dérivée des valeurs de leur contenu, par exemple lors de la vérification (sur) de l'égalité. Naturellement, beaucoup s’attendraient à ce que vous puissiez utiliser n’importe quelle liste [1, 2]
pour obtenir la même clé, où vous devez conserver exactement le même objet de liste. Mais la recherche par valeur est interrompue dès que la liste utilisée comme clé est modifiée, et pour la recherche par identité, vous devez conserver exactement la même liste - ce qui n'est pas nécessaire pour une autre opération de liste courante (du moins, je ne pense à aucune autre opération courante). ).
D'autres objets tels que les modules et object
font de toute façon une affaire beaucoup plus importante de leur identité d'objet (quand était la dernière fois que vous aviez deux objets module distincts appelés sys
?), Et sont comparés par cela en tous cas. Par conséquent, il est moins surprenant - ou même prévu - que, lorsqu'elles sont utilisées comme clés de dictée, elles se comparent également par identité.
Pourquoi ne puis-je pas utiliser une liste en tant que clé dict en python?
>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}
(pour quiconque trébuche sur cette question en cherchant un moyen de la contourner)
comme expliqué par d'autres ici, en effet vous ne pouvez pas. Vous pouvez cependant utiliser sa représentation sous forme de chaîne si vous voulez vraiment utiliser votre liste.
Le problème est que les n-uplets sont immuables et les listes ne le sont pas. Considérer ce qui suit
d = {}
li = [1,2,3]
d[li] = 5
li.append(4)
Qu'est-ce que doit d[li]
revenir? Est-ce la même liste? Que diriez-vous d[[1,2,3]]
? Il a les mêmes valeurs, mais est-ce une liste différente?
En fin de compte, il n'y a pas de réponse satisfaisante. Par exemple, si la seule clé qui fonctionne est la clé d'origine, si vous ne faites pas référence à cette clé, vous ne pourrez plus jamais accéder à la valeur. Avec toutes les autres clés autorisées, vous pouvez construire une clé sans référence à la clé d'origine.
Si mes deux suggestions fonctionnent, vous avez des clés très différentes qui renvoient la même valeur, ce qui est un peu surprenant. Si seul le contenu d'origine fonctionne, votre clé va vite se détériorer, car les listes sont faites pour être modifiées.
Vous venez de trouver que vous pouvez changer la liste en tuple, puis l'utiliser comme clé.
d = {Tuple([1,2,3]): 'value'}
Voici une réponse http://wiki.python.org/moin/DictionaryKeys
Que se passerait-il si vous essayiez d'utiliser des listes comme clés, avec le hachage comme, par exemple, leur emplacement mémoire?
Rechercher des listes différentes avec le même contenu produirait des résultats différents, même si comparer des listes avec le même contenu les indiquerait comme équivalentes.
Qu'en est-il de l'utilisation d'un littéral de liste dans une recherche dans un dictionnaire?
Votre awnser peut être trouvé ici:
Pourquoi les listes ne peuvent pas être des clés de dictionnaire
Les nouveaux arrivants de Python se demandent souvent pourquoi, bien que le langage comprenne à la fois un tuple et un type de liste, les n-uplets peuvent être utilisés comme clés de dictionnaire, alors que les listes ne le sont pas. La meilleure explication est d’abord de comprendre le fonctionnement des dictionnaires Python.
Source et informations supplémentaires: http://wiki.python.org/moin/DictionaryKeys
La réponse simple à votre question est que la liste de classes n’implémente pas la méthode de hachage requise pour tout objet souhaitant être utilisé comme clé dans un dictionnaire. Cependant, la raison pour laquelle un hachage n'est pas implémenté de la même manière que dans la classe Tuple (basée sur le contenu du conteneur) est due au fait qu'une liste est mutable, l’édition de la liste nécessiterait de recalculer le hachage, ce qui pourrait signifier que la liste est maintenant située dans le mauvais compartiment de la table de hachage sous-jacente. Notez que puisque vous ne pouvez pas modifier un tuple (immuable), ce problème ne se pose pas.
En remarque, l'implémentation réelle de la recherche dictobjects est basée sur l'algorithme D de Knuth Vol. 3, sec. 6.4. Si vous avez ce livre à votre disposition, cela pourrait valoir la peine d'être lu. De plus, si vous êtes vraiment très intéressé, vous voudrez peut-être jeter un coup d'œil aux commentaires des développeurs sur le réel implémentation de dictobject ici. = Cela explique en détail comment cela fonctionne. Il existe également une conférence en python sur la mise en œuvre des dictionnaires qui pourrait vous intéresser. Ils définissent une clé et ce qu’est un hash dans les premières minutes.
Comme les listes sont modifiables, les clés dict
(et les membres set
doivent être haschables, et le hachage des objets mutables est une mauvaise idée car les valeurs de hachage devraient être calculé sur la base d'attributs d'instance.
Dans cette réponse, je donnerai quelques exemples concrets, en ajoutant, espérons-le, de la valeur ajoutée aux réponses existantes. Chaque observation s'applique également aux éléments de la structure de données set
.
Exemple 1: hachage d'un objet modifiable où la valeur de hachage est basée sur une caractéristique modifiable de l'objet.
>>> class stupidlist(list):
... def __hash__(self):
... return len(self)
...
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
Après avoir muté stupid
, il ne peut plus être trouvé dans le dict parce que le hachage a changé. Seul un balayage linéaire de la liste des clés de dict trouve stupid
.
Exemple 2: ... mais pourquoi ne pas simplement une valeur de hachage constante?
>>> class stupidlist2(list):
... def __hash__(self):
... return id(self)
...
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>>
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
Ce n’est pas non plus une bonne idée car les objets identiques doivent être hachés de manière identique pour pouvoir être trouvés dans un dict
ou set
.
Exemple: ... ok, qu'en est-il des hachages constants dans toutes les instances?!
>>> class stupidlist3(list):
... def __hash__(self):
... return 1
...
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>>
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
Les choses semblent fonctionner comme prévu, mais réfléchissez à ce qui se passe: lorsque toutes les instances de votre classe produisent la même valeur de hachage, vous aurez une collision de hachage chaque fois qu'il y a plus de deux instances en tant que clés dans un dict
ou dans un présent. dans un set
.
Trouver la bonne instance avec my_dict[key]
Ou key in my_dict
(Ou item in my_set
) Doit effectuer autant de vérifications d'égalité qu'il y a d'instances de stupidlist3
Dans les clés de dict ( au pire des cas). À ce stade, le but du dictionnaire - O(1) lookup -) est complètement annulé. Ceci est démontré dans les timings suivants (réalisés avec IPython).
Quelques timings pour l'exemple
>>> lists_list = [[i] for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>>
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Comme vous pouvez le constater, le test d’adhésion dans notre stupidlists_set
Est encore plus lent que le balayage linéaire sur l’ensemble lists_list
, Alors que vous avez le temps de recherche très rapide attendu (facteur 500) dans un ensemble sans des tas de collisions de hash.
TL; DR: vous pouvez utiliser Tuple(yourlist)
comme clé dict
, car les n-uplets sont immuables et hashable.
Selon la documentation de Python 2.7.2:
Un objet est hashable s'il a une valeur de hachage qui ne change jamais au cours de sa vie (il a besoin d'une méthode hash ()), et peut être comparée à d'autres objets (il a besoin d'un eq () ou cmp ()). Les objets pouvant être lavés qui se comparent égaux doivent avoir la même valeur de hachage.
Hashability rend un objet utilisable en tant que clé de dictionnaire et membre d'un ensemble, car ces structures de données utilisent la valeur de hachage en interne.
Tous les objets intégrés immuables de Python peuvent être hachés, alors qu’aucun conteneur modifiable (tels que des listes ou des dictionnaires) ne l’est. Les objets qui sont des instances de classes définies par l'utilisateur peuvent être divisés par défaut. ils comparent tous inégalement, et leur valeur de hachage est leur id ().
Un tuple est immuable en ce sens que vous ne pouvez pas ajouter, supprimer ou remplacer ses éléments, mais les éléments eux-mêmes peuvent être mutables. La valeur de hachage de List dépend des valeurs de hachage de ses éléments. Elle change donc lorsque vous modifiez les éléments.
L'utilisation d'identifiants pour les hachages de liste impliquerait que toutes les listes se comparent différemment, ce qui serait surprenant et peu pratique.