Pourquoi les ensembles Python ne préservent-ils pas l'ordre d'insertion?

12

J'ai été surpris de découvrir récemment que même si les dict sont garantis pour préserver l'ordre d'insertion dans Python 3.7+, les ensembles ne sont pas:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>> d['d'] = 4
>>> d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> s = {'a', 'b', 'c'}
>>> s
{'b', 'a', 'c'}
>>> s.add('d')
>>> s
{'d', 'b', 'a', 'c'}

Quelle est la justification de cette différence? Les mêmes améliorations d'efficacité qui ont conduit l'équipe Python à changer l'implémentation de dict ne s'appliquent-elles pas également aux ensembles?

Je ne suis pas à la recherche de pointeurs vers des implémentations d'ensembles ordonnés ou de façons d'utiliser des dict comme remplaçants pour des ensembles. Je me demande simplement pourquoi l'équipe Python n'a pas fait en sorte que les ensembles intégrés préservent l'ordre en même temps qu'ils le faisaient pour les dict.

Bart Robinson
la source
1
Est-ce que cela répond à votre question? Python a-t-il un ensemble ordonné?
Mihai Chelaru
1
Non, je comprends que Python ne dispose pas d'un ensemble ordonné intégré. Je me demande simplement pourquoi c'est le cas, puisque les dictés sont désormais ordonnés.
Bart Robinson
4
Les modèles d'utilisation sont différents, ils sont donc optimisés pour différents cas d'utilisation. C'est une idée fausse commune que les ensembles ne sont que des dict avec des valeurs nulles dans CPython, c'est tout à fait incorrect: les implémentations sont différentes. Si votre question n'est pas close, je peux poster une réponse détaillée.
wim
1
"Les modèles d'utilisation sont différents, ils sont donc optimisés pour différents cas d'utilisation." Je pense qu'une bonne réponse à la question serait plus détaillée. La question est de savoir ce qui rend les deux approches différentes optimales pour les cas d'utilisation correspondants.
Karl Knechtel
Notez que PyPy utilise le même ordre pour les deux dictet setdepuis 2.7.
MisterMiyagi

Réponses:

11

Les ensembles et les dict sont optimisés pour différents cas d'utilisation. L'utilisation principale d'un ensemble est le test d'appartenance rapide, qui est indépendant de l'ordre.Pour les dictés, le coût de la recherche est l'opération la plus critique et la clé est plus susceptible d'être présente. Avec les ensembles, la présence ou l'absence d'un élément n'est pas connue à l'avance et l'implémentation de l'ensemble doit donc être optimisée pour le cas trouvé et non trouvé. En outre, certaines optimisations pour les opérations d'ensemble courantes telles que l'union et l'intersection rendent difficile la conservation de l'ordre des ensembles sans dégrader les performances.

Bien que les deux structures de données soient basées sur le hachage, il est faux de penser que les ensembles sont simplement implémentés comme des dict avec des valeurs nulles. Même avant l'implémentation compacte de dict dans CPython 3.6, les implémentations set et dict différaient déjà considérablement, avec peu de réutilisation de code. Par exemple, les dict utilisent le sondage aléatoire, mais les ensembles utilisent une combinaison de sondage linéaire et d'adressage ouvert, pour améliorer la localisation du cache. La sonde linéaire initiale ( 9 étapes par défaut dans CPython) vérifiera une série de paires clé / hachage adjacentes, améliorant les performances en réduisant le coût de la gestion des collisions de hachage - l'accès mémoire consécutif est moins cher que les sondes dispersées.

Il serait possible en théorie de changer l'implémentation d'ensemble de CPython pour qu'elle soit similaire à la dictée compacte, mais dans la pratique, il y a des inconvénients, et des développeurs principaux notables étaient opposés à faire un tel changement.

Les décors ne sont pas classés. (Pourquoi? Les modèles d'utilisation sont différents. De plus, la mise en œuvre est différente.)

- Guido van Rossum

Les ensembles utilisent un algorithme différent qui n'est pas aussi modifiable pour conserver l'ordre d'insertion. Les opérations d'ensemble à ensemble perdent leur flexibilité et leurs optimisations si la commande est requise. Les mathématiques des ensembles sont définies en termes d'ensembles non ordonnés. En bref, la mise en ordre n'est pas dans un avenir immédiat.

- Raymond Hettinger

Une discussion détaillée sur l'opportunité de compacter les ensembles pour 3.7, et des réponses sur les raisons pour lesquelles il a été décidé de ne pas le faire, peuvent être trouvées dans les listes de diffusion python-dev.

En résumé, les points principaux sont que les modèles d'utilisation sont différents (les ordres de commande d'insertion tels que ** kwargs sont utiles , moins pour les ensembles), les économies d'espace pour les ensembles de compactage sont moins importantes (car il n'y a que des clés et un tableau de hachage pour densifier, par opposition aux clés, hachages et valeurs), et l'optimisation de sondage linéaire susmentionnée dans les ensembles est incompatible avec une implémentation compacte.

Je reproduis ci-dessous le billet de Raymond qui couvre les points les plus importants.

Le 14 septembre 2016, à 15 h 50, Eric Snow a écrit:

Ensuite, je ferai de même pour les décors.

Sauf si j'ai mal compris, Raymond était opposé à un changement similaire à définir.

C'est vrai. Voici quelques réflexions sur le sujet avant que les gens ne commencent à courir sauvage.

  • Pour le dict compact, les économies d'espace ont été un gain net avec l'espace supplémentaire consommé par les indices et la surutilisation des tableaux clé / valeur / hachage étant plus que compensée par l'amélioration de la densité des tableaux clé / valeur / hachage. Cependant, pour les ensembles, le net était beaucoup moins favorable car nous avons encore besoin des indices et de la surutilisation mais ne pouvons compenser le coût d'espace qu'en densifiant seulement deux des trois tableaux. En d'autres termes, le compactage a plus de sens lorsque vous avez perdu de l'espace pour les clés, les valeurs et les hachages. Si vous perdez l'un de ces trois, il cesse d'être convaincant.

  • Le modèle d'utilisation des ensembles est différent des modèles. Le premier a plus de recherches à succès. Ce dernier a tendance à avoir moins de recherches de clés manquantes. De plus, certaines des optimisations pour les opérations set-to-set rendent difficile la conservation de l'ordre des sets sans affecter les performances.

  • J'ai poursuivi une voie alternative pour améliorer les performances de l'ensemble. Au lieu de compacter (ce qui n'était pas beaucoup de gain d'espace et entraînait le coût d'une indirection supplémentaire), j'ai ajouté une sonde linéaire pour réduire le coût des collisions et améliorer les performances du cache. Cette amélioration est incompatible avec l'approche de compactage que j'ai préconisée pour les dictionnaires.

  • Pour l'instant, l'effet secondaire de commande sur les dictionnaires n'est pas garanti, il est donc prématuré de commencer à insister pour que les ensembles deviennent également ordonnés. Les documents établissent déjà un lien vers une recette pour créer un ensemble ordonné ( https://code.activestate.com/recipes/576694/ ), mais il semble que l'adoption ait été presque nulle. De plus, maintenant qu'Eric Snow nous a donné un OrderedDict rapide, il est plus facile que jamais de construire un OrderedSet à partir de MutableSet et OrderedDict, mais encore une fois je n'ai observé aucun intérêt réel parce que l'analyse de données set-to-set typique n'a pas vraiment besoin ou souci de commander. De même, l'utilisation principale des tests d'appartenance rapides est indépendante de l'ordre.

  • Cela dit, je pense qu'il est possible d'ajouter des implémentations d'ensembles alternatifs à PyPI. En particulier, il existe des cas spéciaux intéressants pour les données commandables où les opérations set-to-set peuvent être accélérées en comparant des plages entières de clés (voir https://code.activestate.com/recipes/230113-implementation-of- sets-using-sorted-lists pour un point de départ). IIRC, PyPI a déjà du code pour les filtres de floraison et le hachage de coucous.

  • Je comprends qu'il est excitant d'avoir un bloc de code majeur accepté dans le noyau Python, mais cela ne devrait pas ouvrir la porte à des réécritures plus importantes d'autres types de données, sauf si nous sommes sûrs que cela est justifié.

- Raymond Hettinger

De [Python-Dev] Python 3.6 dict devient compact et obtient une version privée; et les mots clés sont commandés , septembre 2016.

wim
la source
Je vous remercie! J'ai accepté cette réponse car elle répond le plus directement à la question d'origine. La réponse de pylang ci-dessous contient également des liens utiles vers des discussions plus récentes entre les développeurs Python.
Bart Robinson Il y a
3

Discussions

Votre question est pertinente et a déjà été longuement discutée sur les développeurs python il n'y a pas longtemps. R. Hettinger a partagé une liste de justifications dans ce fil . L'état de la question semble illimité maintenant, peu de temps après cette réponse détaillée de T. Peters.

En bref, la mise en œuvre de dict modernes qui préserve l'ordre d'insertion est unique et n'est pas considérée comme appropriée avec les ensembles. En particulier, les dict sont utilisés partout pour exécuter Python (par exemple __dict__dans les espaces de noms des objets). Une motivation majeure derrière le dict moderne était de réduire la taille, ce qui rend Python plus efficace en mémoire dans son ensemble. En revanche, les ensembles sont moins répandus que les dits dans le noyau de Python et dissuadent ainsi une telle refactorisation. Voir aussi le discours de R. Hettinger sur la mise en œuvre du dict moderne.


Points de vue

La nature non ordonnée des ensembles en Python est parallèle au comportement des ensembles mathématiques . L'ordre n'est pas garanti.

Le concept mathématique correspondant n'est pas ordonné et il serait étrange d'imposer tel que l'ordre - R. Hettinger

Si un ordre quelconque était introduit dans les ensembles en Python, ce comportement respecterait une structure mathématique complètement distincte, à savoir un ensemble ordonné (ou Oset). Les osets jouent un rôle distinct en mathématiques, en particulier en combinatoire. Une application pratique des Osets est observée dans le changement de cloches .

Le fait d'avoir des ensembles non ordonnés est compatible avec une structure de données très générique et omniprésente qui détache la plupart des mathématiques modernes, c'est-à-dire la théorie des ensembles . Je soumets, les ensembles non ordonnés en Python sont bons à avoir.

Voir également les articles connexes qui développent ce sujet:

pylang
la source