¿Se ordenan los diccionarios en Python 3.6+?

470

Los diccionarios se ordenan en Python 3.6 (al menos en la implementación de CPython) a diferencia de las encarnaciones anteriores. Esto parece un cambio sustancial, pero es solo un breve párrafo en la documentación . Se describe como un detalle de implementación de CPython en lugar de una función de lenguaje, pero también implica que esto puede convertirse en estándar en el futuro.

¿Cómo funciona mejor la nueva implementación del diccionario que la anterior al tiempo que conserva el orden de los elementos?

Aquí está el texto de la documentación:

dict()ahora usa una representación "compacta" iniciada por PyPy . El uso de memoria del nuevo dict () es entre un 20% y un 25% menor en comparación con Python 3.5. PEP 468 (Preservar el orden de ** kwargs en una función) es implementado por esto. El aspecto de preservación del orden de esta nueva implementación se considera un detalle de implementación y no se debe confiar en él (esto puede cambiar en el futuro, pero se desea tener esta nueva implementación de dict en el idioma durante algunos lanzamientos antes de cambiar la especificación del idioma para ordenar la semántica de preservación del orden para todas las implementaciones actuales y futuras de Python; esto también ayuda a preservar la compatibilidad con versiones anteriores del lenguaje donde el orden de iteración aleatoria todavía está vigente, por ejemplo, Python 3.5). (Contribuido por INADA Naoki ennúmero 27350 . Idea sugerida originalmente por Raymond Hettinger .)

Actualización de diciembre de 2017: dictel orden de inserción de retención está garantizado para Python 3.7

Chris_Rands
fuente
2
Vea este hilo en la lista de correo de Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html si no lo ha visto; Es básicamente una discusión sobre estos temas.
mgc
1
Si ahora se supone que los kwargs deben ordenarse (lo cual es una buena idea) y los kwargs son dict, no OrderedDict, entonces supongo que se podría suponer que las claves dict seguirán ordenadas en la futura versión de Python, a pesar de que la documentación dice lo contrario.
Dmitriy Sintsov
44
@DmitriySintsov No, no hagas esa suposición. Este fue un problema que surgió durante la redacción del PEP que define la característica de preservación del orden **kwargsy, como tal, la redacción utilizada es diplomática: **kwargsen una función, la firma ahora se garantiza que es un mapeo de preservación del orden de inserción . Han utilizado el término mapeo para no forzar ninguna otra implementación para hacer que el dict sea ordenado (y usarlo OrderedDictinternamente) y como una forma de indicar que no se supone que esto dependa del hecho de que dictno está ordenado.
Dimitris Fasarakis Hilliard
77
Una buena explicación en video de Raymond Hettinger
Alex
1
@wazoox, el orden y la complejidad del hashmap no ha cambiado. El cambio hace que el hashmap sea más pequeño al desperdiciar menos espacio, y el espacio guardado es (¿generalmente?) Más de lo que toma la matriz auxiliar. Más rápido, más pequeño, ordenado: puedes elegir los 3.
John La Rooy

Respuestas:

513

¿Se ordenan los diccionarios en Python 3.6+?

Se ordenan por inserción [1] . A partir de Python 3.6, para la implementación CPython de Python, los diccionarios recuerdan el orden de los elementos insertados . Esto se considera un detalle de implementación en Python 3.6 ; debe usarlo OrderedDictsi desea un orden de inserción garantizado en otras implementaciones de Python (y otro comportamiento ordenado [1] ).

A partir de Python 3.7 , esto ya no es un detalle de implementación, sino que se convierte en una característica del lenguaje. De un mensaje python-dev de GvR :

Hazlo así. "Dict mantiene orden de inserción" es el fallo. ¡Gracias!

Esto simplemente significa que puede confiar en ello . Otras implementaciones de Python también deben ofrecer un diccionario de inserción ordenada si desean ser una implementación conforme de Python 3.7.


¿Cómo funciona mejor la 3.6implementación del diccionario Python [2] que la anterior al tiempo que conserva el orden de los elementos?

Básicamente, manteniendo dos matrices .

  • La primera matriz, dk_entriescontiene las entradas ( de tipoPyDictKeyEntry ) para el diccionario en el orden en que se insertaron. El orden de preservación se logra al ser una matriz de agregar solo donde siempre se insertan nuevos elementos al final (orden de inserción).

  • El segundo, dk_indicescontiene los índices para la dk_entriesmatriz (es decir, valores que indican la posición de la entrada correspondiente en dk_entries). Esta matriz actúa como la tabla hash. Cuando se codifica una clave, conduce a uno de los índices almacenados dk_indicesy la entrada correspondiente se obtiene mediante indexación dk_entries. Dado que solo se mantienen los índices, el tipo de esta matriz depende del tamaño general del diccionario (que va desde el tipo int8_t( 1byte) hasta int32_t/ int64_t( 4/ 8bytes) en las compilaciones 32/ 64bit)

En la implementación anterior, se tenía que asignar una matriz dispersa de tipo PyDictKeyEntryy tamaño dk_size; desafortunadamente, también resultó en mucho espacio vacío ya que no se permitió que esa matriz estuviera más que 2/3 * dk_sizellena por razones de rendimiento . (¡y el espacio vacío todavía tenía PyDictKeyEntrytamaño!).

Este no es el caso ahora, ya que solo se almacenan las entradas requeridas (las que se han insertado) y se mantiene una matriz dispersa de tipo intX_t( Xdependiendo del tamaño del dict) 2/3 * dk_sizellena. El espacio vacío cambió de tipo PyDictKeyEntrya intX_t.

Entonces, obviamente, crear una matriz dispersa de tipos PyDictKeyEntryrequiere mucha más memoria que una matriz dispersa para almacenar ints.

Puede ver la conversación completa en Python-Dev con respecto a esta característica si está interesado, es una buena lectura.


En la propuesta original hecha por Raymond Hettinger , se puede ver una visualización de las estructuras de datos utilizadas que captura la esencia de la idea.

Por ejemplo, el diccionario:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

actualmente está almacenado como [keyhash, clave, valor]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

En cambio, los datos deben organizarse de la siguiente manera:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Como puede ver visualmente ahora, en la propuesta original, una gran cantidad de espacio está esencialmente vacío para reducir las colisiones y agilizar las búsquedas. Con el nuevo enfoque, reduce la memoria requerida al mover la escasez donde realmente se requiere, en los índices.


[1]: Digo "inserción ordenada" y no "ordenada" ya que, con la existencia de OrderedDict, "ordenada" sugiere un comportamiento adicional que el dictobjeto no proporciona . Los OrderedDicts son reversibles, proporcionan métodos sensibles al orden y, principalmente, proporcionan una prueba de igualdad sensible al orden ( ==, !=). dictActualmente no ofrecemos ninguno de esos comportamientos / métodos.


[2]: Las nuevas implementaciones de diccionario funcionan mejor en cuanto a memoria al estar diseñadas de manera más compacta; Ese es el principal beneficio aquí. En cuanto a la velocidad, la diferencia no es tan drástica, hay lugares donde el nuevo dict podría introducir leves regresiones ( búsquedas de teclas, por ejemplo ), mientras que en otros (la iteración y el cambio de tamaño vienen a la mente) un aumento de rendimiento debería estar presente.

En general, el rendimiento del diccionario, especialmente en situaciones de la vida real, mejora debido a la compacidad introducida.

Dimitris Fasarakis Hilliard
fuente
15
Entonces, ¿qué sucede cuando se elimina un artículo? ¿Se ha entriescambiado el tamaño de la lista? o se mantiene un espacio en blanco? o está comprimido de vez en cuando?
njzk2
18
@ njzk2 Cuando se elimina un elemento, el índice correspondiente se reemplaza por DKIX_DUMMYun valor de -2y la entrada en la entrymatriz se reemplaza porNULL , cuando se realiza la inserción, los nuevos valores se agregan a la matriz de entradas, aún no he podido discernir, pero bastante seguro cuando se llenan los índices más allá del 2/3umbral, se realiza el cambio de tamaño. Esto puede llevar a una reducción en lugar de crecer si DUMMYexisten muchas entradas.
Dimitris Fasarakis Hilliard
3
@Chris_Rands No, la única regresión real que he visto está en el rastreador en un mensaje de Victor . Aparte de ese microbenchmark, no he visto ningún otro problema / mensaje que indique una seria diferencia de velocidad en las cargas de trabajo de la vida real. Hay lugares donde el nuevo dict podría introducir regresiones leves (búsquedas de teclas, por ejemplo), mientras que en otros (la iteración y el cambio de tamaño vienen a la mente) un aumento de rendimiento estaría presente.
Dimitris Fasarakis Hilliard
3
Corrección en la parte de cambio de tamaño : los diccionarios no cambian de tamaño cuando elimina elementos, se vuelven a calcular cuando los vuelve a insertar. Por lo tanto, si un dict se crea con d = {i:i for i in range(100)}y que .poptodos los elementos w / o la inserción, el tamaño no va a cambiar. Cuando se agrega nuevamente, d[1] = 1se calcula el tamaño apropiado y se redimensiona el dict.
Dimitris Fasarakis Hilliard
66
@ Chris_Rands Estoy bastante seguro de que se queda. La cuestión es que, y la razón por la que cambié mi respuesta para eliminar las declaraciones generales sobre ' dictser ordenado', dictno se ordenó en el sentido que sí OrderedDict. La cuestión notable es la igualdad. dicts tienen orden insensible ==, OrderedDicts tienen orden sensible. Volcar OrderedDicty cambiar dictspara tener ahora comparaciones sensibles al orden podría provocar una gran ruptura en el código antiguo. Supongo que lo único que puede cambiar sobre OrderedDicts es su implementación.
Dimitris Fasarakis Hilliard
67

A continuación se responde la primera pregunta original:

¿Debo usar dicto OrderedDicten Python 3.6?

Creo que esta oración de la documentación es suficiente para responder a su pregunta.

El aspecto de preservación del orden de esta nueva implementación se considera un detalle de implementación y no se debe confiar en él

dictno está destinado explícitamente a ser una colección ordenada, por lo tanto, si desea mantenerse constante y no confiar en un efecto secundario de la nueva implementación, debe seguir OrderedDict.

Haga su código a prueba de futuro :)

Hay un debate sobre eso aquí .

EDITAR: Python 3.7 mantendrá esto como una función ver

Maresh
fuente
1
Parece que si no pretendían que fuera una característica real sino solo un detalle de implementación, entonces ni siquiera deberían incluirlo en la documentación.
xji
3
No estoy seguro acerca de su advertencia de edición; ya que la garantía sólo se aplica para Python 3.7, supongo que el consejo para Python 3.6 es sin cambios, es decir, predice están ordenados en CPython pero no cuentan en él
Chris_Rands
25

Actualización: Guido van Rossum anunció en la lista de correo que a partir de Python 3.7 dicts en todas las implementaciones de Python debe preservar el orden de inserción.

fjsj
fuente
2
Ahora que el pedido de claves es el estándar oficial, ¿para qué sirve el OrderedDict? ¿O es ahora redundante?
Jonny Waffles
2
Supongo que OrderedDict no será redundante porque tiene el move_to_endmétodo y su igualdad es sensible al orden: docs.python.org/3/library/… . Ver la nota sobre la respuesta de Jim Fasarakis Hilliard.
fjsj
@JonnyWaffles ve la respuesta de Jim y este Q&A stackoverflow.com/questions/50872498/…
Chris_Rands
3
Si desea que su código se ejecute igual en 2.7 y 3.6 / 3.7 +, debe usar OrderedDict
boatcoder
3
Probablemente habrá un "UnorderedDict" pronto para las personas que les gusta molestar sus dictados por razones de seguridad; p
ZF007
9

Quería agregar a la discusión anterior, pero no tengo la reputación de comentar.

Python 3.8 aún no se ha lanzado del todo, pero incluso incluirá la reversed()función en los diccionarios (eliminando otra diferencia de OrderedDict.

Dict y dictviews ahora son iterables en orden de inserción invertido usando reversed (). (Contribución de Rémi Lapeyre en bpo-33462.) Vea las novedades de python 3.8

No veo ninguna mención del operador de igualdad u otras características, por OrderedDictlo que todavía no son del todo iguales.

rkengler
fuente