Les dictionnaires sont-ils ordonnés dans Python 3.6+ ?
Ils sont insertion commandée [1] . À partir de Python 3.6, pour l'implémentation CPython de Python, les dictionnaires mémoriser l'ordre des éléments insérés . Ceci est considéré comme un détail d'implémentation dans Python 3.6. ; vous devez utiliser OrderedDict
si vous voulez un ordre d'insertion, c'est garanti à travers d'autres implémentations de Python (et d'autres comportements ordonnés [1] ).
À partir de Python 3.7 Dans le cas d'un système de gestion de l'information, il ne s'agit plus d'un détail de mise en œuvre, mais d'une caractéristique du langage. Extrait d'un message de GvR sur python-dev :
Faites-le. "Dict conserve l'ordre d'insertion" est la règle. Merci !
Cela signifie simplement que vous pouvez compter sur elle . Les autres implémentations de Python doivent également proposer un dictionnaire à insertion ordonnée si elles souhaitent être une implémentation conforme de Python 3.7.
Comment le programme Python 3.6
La mise en œuvre du dictionnaire donne de meilleurs résultats [2] que l'ancien tout en préservant l'ordre des éléments ?
Essentiellement, en garder deux tableaux .
-
Le premier tableau, dk_entries
contient les entrées ( de type PyDictKeyEntry
) pour le dictionnaire dans l'ordre où ils ont été insérés. La préservation de l'ordre est assurée par le fait qu'il s'agit d'un tableau à ajouter uniquement où les nouveaux éléments sont toujours insérés à la fin (ordre d'insertion).
-
La seconde, dk_indices
contient les indices pour les dk_entries
(c'est-à-dire des valeurs qui indiquent la position de l'entrée correspondante dans le tableau des dk_entries
). Ce tableau fait office de table de hachage. Lorsqu'une clé est hachée, elle conduit à l'un des indices stockés dans dk_indices
et l'entrée correspondante est récupérée en indexant dk_entries
. Puisque seuls les indices sont conservés, le type de ce tableau dépend de la taille globale du dictionnaire (allant du type int8_t
( 1
octet) à int32_t
/ int64_t
( 4
/ 8
octets) sur 32
/ 64
les constructions de bits)
Dans l'implémentation précédente, un tableau clairsemé de type PyDictKeyEntry
et la taille dk_size
a dû être allouée ; malheureusement, cela a également donné lieu à beaucoup d'espace vide puisque ce tableau ne devait pas dépasser 2/3 * dk_size
complet pour des raisons de performance . (et l'espace vide toujours avait PyDictKeyEntry
taille !).
Ce n'est pas le cas aujourd'hui puisque seule la requis sont stockées (celles qui ont été insérées) et un tableau clairsemé de type intX_t
( X
selon la taille des dictées) 2/3 * dk_size
s plein est conservé. L'espace vide est passé du type PyDictKeyEntry
a intX_t
.
Donc, évidemment, la création d'un tableau clairsemé de type PyDictKeyEntry
est beaucoup plus gourmand en mémoire qu'un tableau clairsemé pour stocker int
s.
Vous pouvez voir l'intégralité de la conversation sur Python-Dev concernant cette fonctionnalité si cela vous intéresse, c'est une bonne lecture.
Dans la proposition originale faite par Raymond Hettinger Dans le cadre de l'étude de l'OCDE, on peut voir une visualisation des structures de données utilisées, ce qui permet de saisir l'essentiel de l'idée.
Par exemple, le dictionnaire :
d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}
est actuellement stocké sous la forme [keyhash, key, value] :
entries = [['--', '--', '--'],
[-8522787127447073495, 'barry', 'green'],
['--', '--', '--'],
['--', '--', '--'],
['--', '--', '--'],
[-9092791511155847987, 'timmy', 'red'],
['--', '--', '--'],
[-6480567542315338377, 'guido', 'blue']]
Au lieu de cela, les données devraient être organisées comme suit :
indices = [None, 1, None, None, None, 0, None, 2]
entries = [[-9092791511155847987, 'timmy', 'red'],
[-8522787127447073495, 'barry', 'green'],
[-6480567542315338377, 'guido', 'blue']]
Comme vous pouvez maintenant le constater visuellement, dans la proposition originale, une grande partie de l'espace est essentiellement vide pour réduire les collisions et rendre les recherches plus rapides. Avec la nouvelle approche, vous réduisez la mémoire requise en déplaçant l'espacement là où il est vraiment nécessaire, dans les indices.
[1] : Je dis "insertion ordonnée" et non "ordonnée" car, avec l'existence d'OrderedDict, "ordonné" suggère un comportement supplémentaire que l'objet dict
ne fournit pas. Les OrderedDicts sont réversibles, fournissent des méthodes sensibles à l'ordre et, principalement, fournissent un test d'égalité sensible à l'ordre (==
, !=
). Les dict
s n'offrent actuellement aucun de ces comportements/méthodes.
[2] : Les nouvelles implémentations de dictionnaires sont plus performantes en termes de mémoire car elles sont conçues de manière plus compacte ; c'est le principal avantage ici. En ce qui concerne la vitesse, la différence n'est pas si radicale, il y a des endroits où le nouveau dictionnaire pourrait introduire de légères régressions ([recherche de clés, par exemple][10]) alors que dans d'autres (l'itération et le redimensionnement viennent à l'esprit) une augmentation des performances devrait être présente. Dans l'ensemble, les performances du dictionnaire, notamment en situation réelle, s'améliorent grâce à la compacité introduite.
3 votes
Voir ce fil de discussion sur la liste de diffusion Python-Dev : mail.python.org/pipermail/python-dev/2016-septembre/146327.html si vous ne l'avez pas vu ; il s'agit essentiellement d'une discussion autour de ces sujets.
0 votes
Info aquí de Raymon Hettinger incluant la recette du code original pour le nouveau dict. Il est intéressant de noter qu'il dit : "Au moment où elle a été présentée, l'humeur était opposée à ce que les dicts soient ordonnés, donc cette recette [originale] remplit intentionnellement les valeurs supprimées avec la dernière entrée de la liste."
2 votes
Si les kwargs sont maintenant censés être ordonnés (ce qui est une bonne idée) et que les kwargs sont des dict, et non OrderedDict, alors je suppose que l'on peut supposer que les clés des dict resteront ordonnées dans la future version de Python, bien que la documentation dise le contraire.
6 votes
@DmitriySintsov Non, ne faites pas cette supposition. C'est un problème qui a été soulevé lors de la rédaction du PEP qui définit la fonction de préservation de l'ordre de la fonction
**kwargs
et en tant que telle, la formulation utilisée est diplomatique :**kwargs
dans la signature d'une fonction est maintenant garantie comme étant une insertion préservant l'ordre. cartographie . Ils ont utilisé le terme cartographie afin de ne pas forcer d'autres implémentations à rendre le dict ordonné (et à utiliser un fichier de typeOrderedDict
en interne) et comme moyen de signaler que cela n'est pas censé dépendre du fait que l'élémentdict
n'est pas commandée.10 votes
Un bon explication vidéo de Raymond Hettinger
1 votes
@wazoox, l'ordre et la complexité du hashmap n'ont pas changé. Le changement rend le hashmap plus petit en gaspillant moins d'espace, et l'espace économisé est (généralement ?) plus que ce que le tableau auxiliaire prend. Plus rapide, plus petit, ordonné - vous pouvez choisir les 3.
0 votes
Tout moyen d'avoir
OrderedDict
est automatiquement converti en ordinairedict
s dans Python 3.7, ou doit-on changer manuellement en testant la version de Python en cours d'exécution ?1 votes
@martineau Cela mériterait peut-être une question séparée, mais pas à ma connaissance. L'avantage en termes de performance de la commutation, je suppose, serait léger. De plus, vous pourriez vouloir un
OrderedDict
encore, même dans Python 3.7 stackoverflow.com/questions/50872498/0 votes
Chris : Bons points dans la réponse liée. Je pense qu'il y a un grand nombre de
OrderedDict
Mais il est assez facile de tester la version de Python utilisée et de choisir celle que vous voulez lorsqu'elles peuvent être utilisées de manière interchangeable.