¿Por qué los conjuntos de Python no conservan el orden de inserción?

12

Me sorprendió descubrir recientemente que, si bien los dictos están garantizados para preservar el orden de inserción en Python 3.7+, los conjuntos no son:

>>> 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'}

¿Cuál es la razón de esta diferencia? ¿Las mismas mejoras de eficiencia que llevaron al equipo de Python a cambiar la implementación de dict no se aplican también a los conjuntos?

No busco punteros para implementaciones de conjuntos ordenados o formas de usar dictos como sustitutos para conjuntos. Me pregunto por qué el equipo de Python no hizo que los conjuntos incorporados conservaran el orden al mismo tiempo que lo hicieron para los dictados.

Bart Robinson
fuente
1
¿Responde esto a tu pregunta? ¿Python tiene un conjunto ordenado?
Mihai Chelaru
1
No, entiendo que Python no tiene un conjunto ordenado incorporado. Me pregunto por qué ese es el caso, ya que los dictados ahora están ordenados.
Bart Robinson
44
Los patrones de uso son diferentes, por lo que están optimizados para diferentes casos de uso. Es un error común pensar que los conjuntos son solo dictados con valores nulos en CPython, eso es completamente incorrecto: las implementaciones son diferentes. Si su pregunta no se cierra, puedo publicar una respuesta detallada.
wim
1
"Los patrones de uso son diferentes, por lo que están optimizados para diferentes casos de uso". Creo que una buena respuesta a la pregunta sería más detallada. La pregunta es sobre qué hace que los dos enfoques diferentes sean óptimos para los casos de uso correspondientes.
Karl Knechtel
Tenga en cuenta que PyPy usa el mismo orden para ambos dicty setdesde 2.7.
MisterMiyagi

Respuestas:

10

Los conjuntos y los dictos están optimizados para diferentes casos de uso. El uso principal de un conjunto es la prueba rápida de membresía, que es independiente del orden. Para los dictados, el costo de la búsqueda es la operación más crítica, y es más probable que la clave esté presente. Con los conjuntos, la presencia o ausencia de un elemento no se conoce de antemano, por lo que la implementación del conjunto debe optimizarse tanto para el caso encontrado como para el no encontrado. Además, algunas optimizaciones para las operaciones de conjuntos comunes, como la unión y la intersección, dificultan la retención del orden de conjuntos sin degradar el rendimiento.

Si bien ambas estructuras de datos están basadas en hash, es un error común pensar que los conjuntos solo se implementan como dictados con valores nulos. Incluso antes de la implementación compacta dict en CPython 3.6, las implementaciones set y dict ya diferían significativamente, con poca reutilización de código. Por ejemplo, los dictos usan sondeo aleatorio, pero los conjuntos usan una combinación de sondeo lineal y direccionamiento abierto, para mejorar la localidad de caché. La sonda lineal inicial ( 9 pasos predeterminados en CPython) verificará una serie de pares clave / hash adyacentes, mejorando el rendimiento al reducir el costo del manejo de colisiones hash: el acceso consecutivo a la memoria es más barato que las sondas dispersas.

En teoría, sería posible cambiar la implementación establecida de CPython para que sea similar al dict compacto, pero en la práctica hay inconvenientes, y los desarrolladores principales notables se opusieron a hacer tal cambio.

Los conjuntos permanecen sin ordenar. (¿Por qué? Los patrones de uso son diferentes. Además, la implementación es diferente).

- Guido van Rossum

Los conjuntos utilizan un algoritmo diferente que no es tan fácil de retener el orden de inserción. Las operaciones de conjunto a conjunto pierden su flexibilidad y optimizaciones si se requiere orden. Las matemáticas de conjuntos se definen en términos de conjuntos no ordenados. En resumen, el orden establecido no está en el futuro inmediato.

- Raymond Hettinger

Puede encontrar una discusión detallada sobre si se compactarán conjuntos para 3.7, y respuestas sobre por qué se decidió en contra, en las listas de correo python-dev.

En resumen, los puntos principales son que los patrones de uso son diferentes (los dictados de orden de inserción como ** kwargs son útiles , menos para los conjuntos), el ahorro de espacio para los conjuntos de compactación es menos significativo (porque solo hay claves y matrices hash para densificar, en lugar de claves, valores hash y valores), y la optimización de sondeo lineal antes mencionada en conjuntos es incompatible con una implementación compacta.

Reproduciré la publicación de Raymond a continuación que cubre los puntos más importantes.

El 14 de septiembre de 2016, a las 3:50 PM, Eric Snow escribió:

Entonces, haré lo mismo con los sets.

A menos que haya entendido mal, Raymond se opuso a hacer un cambio similar para establecer.

Así es. Aquí hay algunos pensamientos sobre el tema antes de que la gente comience a correr salvajemente.

  • Para el dict compacto, el ahorro de espacio fue una ganancia neta con el espacio adicional consumido por los índices y la sobreasignación para las matrices de clave / valor / hash que se compensó con creces por la densidad mejorada de las matrices de clave / valor / hash. Sin embargo, para los conjuntos, la red fue mucho menos favorable porque todavía necesitamos los índices y la sobreasignación, pero solo puede compensar el costo de espacio al densificar solo dos de los tres conjuntos. En otras palabras, la compactación tiene más sentido cuando se desperdicia espacio para claves, valores y valores hash. Si pierde uno de esos tres, deja de ser convincente.

  • El patrón de uso para conjuntos es diferente de los dictados. El primero tiene más búsquedas al azar. Este último tiende a tener menos búsquedas de claves faltantes. Además, algunas de las optimizaciones para las operaciones de conjunto a conjunto dificultan la retención del orden de conjuntos sin afectar el rendimiento.

  • Seguí un camino alternativo para mejorar el rendimiento del set. En lugar de compactar (que no era mucho espacio para ganar e incurría en el costo de una indirección adicional), agregué un sondeo lineal para reducir el costo de las colisiones y mejorar el rendimiento de la caché. Esta mejora es incompatible con el enfoque de compactación que recomendé para los diccionarios.

  • Por ahora, el efecto secundario de ordenar en los diccionarios no está garantizado, por lo que es prematuro comenzar a insistir en que los conjuntos también se ordenen. Los documentos ya enlazan con una receta para crear un OrderedSet ( https://code.activestate.com/recipes/576694/ ) pero parece que la aceptación ha sido casi nula. Además, ahora que Eric Snow nos ha dado un OrderedDict rápido, es más fácil que nunca construir un OrderedSet a partir de MutableSet y OrderedDict, pero nuevamente no he observado ningún interés real porque los análisis de datos típicos de set-to-set realmente no Necesita o se preocupa por ordenar. Del mismo modo, el uso principal de las pruebas rápidas de membresía es independiente del orden.

  • Dicho esto, creo que hay espacio para agregar implementaciones de conjuntos alternativos a PyPI. En particular, hay algunos casos especiales interesantes para los datos que se pueden ordenar en los que las operaciones de conjunto a conjunto se pueden acelerar comparando rangos completos de claves (consulte https://code.activestate.com/recipes/230113-implementation-of- establece-usando-listas-ordenadas para un punto de partida). IIRC, PyPI ya tiene código para filtros de floración y hash cuckoo.

  • Comprendo que es emocionante tener un gran bloque de código aceptado en el núcleo de Python, pero eso no debería abrirse a las compuertas para participar en reescrituras más importantes de otros tipos de datos a menos que estemos seguros de que está garantizado.

- Raymond Hettinger

De [Python-Dev] Python 3.6 dict se vuelve compacto y obtiene una versión privada; y las palabras clave se ordenan , septiembre de 2016.

wim
fuente
2

Discusiones

Su pregunta es pertinente y ya se ha debatido mucho en python-devs no hace mucho tiempo. R. Hettinger compartió una lista de fundamentos en ese hilo . El estado del problema parece abierto ahora, poco después de esta respuesta detallada de T. Peters.

En resumen, la implementación de dictos modernos que conserva el orden de inserción es única y no se considera apropiada con los conjuntos. En particular, los dictos se utilizan en todas partes para ejecutar Python (por ejemplo, __dict__en espacios de nombres de objetos). Una motivación importante detrás del dict moderno fue reducir el tamaño, haciendo que Python sea más eficiente en memoria en general. Por el contrario, los conjuntos son menos frecuentes que los dictados dentro del núcleo de Python y, por lo tanto, disuaden tal refactorización. Ver también la charla de R. Hettinger sobre la implementación moderna del dict.


Perspectivas

La naturaleza desordenada de los conjuntos en Python es paralela al comportamiento de los conjuntos matemáticos . El pedido no está garantizado.

El concepto matemático correspondiente no está ordenado y sería extraño imponerlo como el orden: R. Hettinger

Si se introdujera un orden de cualquier tipo en conjuntos en Python, entonces este comportamiento cumpliría con una estructura matemática completamente separada, a saber, un conjunto ordenado (u Oset). Las Osets juegan un rol separado en matemáticas, particularmente en combinatoria. Se observa una aplicación práctica de Osets en el cambio de campanas .

Tener conjuntos desordenados es coherente con una estructura de datos muy genérica y ubicua que desenreda la matemática más moderna, es decir, la teoría de conjuntos . Presento que es bueno tener conjuntos desordenados en Python.

Vea también publicaciones relacionadas que se expanden sobre este tema:

pylang
fuente