¿Por qué los diccionarios de python no son reversibles para python3.7?

11

A partir de 3.7, se garantiza que los diccionarios estándar de Python mantendrán el orden de inserción. (*)

d = {'b': 1, 'a': 2}
for k in d: 
    print(k)
# Prints always 'b' before 'a'.

En otras palabras, las claves dict se mantienen en un orden estricto. En principio, esto permitiría que las claves sean reversibles. Sin embargo, ninguno de los siguientes trabajos:

# TypeError: 'dict' object is not reversible
for k in reversed(d): 
    print(k)

# TypeError: 'dict_keys' object is not reversible
for k in reversed(d.keys()): 
    print(k)

Preguntas: ¿Cuál es el razonamiento detrás de este comportamiento? ¿Por qué los dictados no se han hecho reversibles? ¿Hay planes para cambiar este comportamiento en el futuro?

La solución, por supuesto, funciona:

for k in reversed(list(d.keys())): 
    print(k)

(*) De hecho, este es el caso ya para las instalaciones típicas de Python 3.6, como se discutió en esta publicación .


Actualización : Comenzando con python 3.8, los dictados son realmente reversibles. La respuesta aceptada se refiere a la discusión entre Guido y otros desarrolladores principales que llevaron a esta decisión. En pocas palabras, ponderaron la coherencia del lenguaje frente a los esfuerzos de implementación y los beneficios reales para los usuarios.

normanius
fuente

Respuestas:

5

De los documentos :

invertido ( seq )

Devuelve un reverso iterator. seq debe ser un objeto que tenga un __reversed__()método o admita el protocolo de secuencia (el __len__()método y el __getitem__()método con argumentos enteros que comienzan en 0).

Un dictobjeto no se implementa __reversed__. Implementa ambos métodos. Sin embargo, __getitem__toma las claves como argumentos, en lugar de enteros (comenzando en 0).

En cuanto a por qué, esto ya ha sido sugerido y discutido aquí .

EDITAR:

Estas citas son de la lista de correo Python-Dev (hilo "Agregar métodos __reversed__ para dict", comenzó el 25. 05. 18), comenzaré con los argumentos "conceptuales", el primero es de Antoine Pitrou:

No vale nada que OrderedDict ya sea compatible con reverse (). El argumento podría ir en ambos sentidos:

  1. dict es similar a OrderedDict hoy en día, por lo que también debería ser compatible con reverse ();

  2. puedes usar OrderedDict para indicar explícitamente que te importa hacer un pedido; No es necesario agregar nada para dictar.

Creo que el orden de inserción garantizado para los dictos regulares es completamente nuevo, por lo que tomará un tiempo para que la noción se asiente y se convierta en parte del pensamiento cotidiano sobre los dictados. Una vez que eso suceda, es probable que surjan casos de uso y que se agregue __reversión__ en algún momento. La implementación parece sencilla y no es un gran salto conceptual esperar que una colección ordenada finita sea reversible.

Seguido por la respuesta de Raymond Hettinger:

Dado que los dictados ahora rastrean el orden de inserción, parece razonable querer conocer las inserciones más recientes (es decir, recorrer las tareas agregadas más recientemente en un dict de tarea). Otros posibles casos de uso probablemente corresponderán a cómo usamos el comando de cola de Unix.

Si surgen esos casos de uso, sería bueno que __reversed__ ya sea compatible para que las personas no se vean tentadas a implementar una solución fea usando llamadas popitem () seguidas de reinserciones.

La principal preocupación expresada en la lista de correo era que esto agregaría demasiada hinchazón o reduciría la eficiencia de la memoria (tener que tener listas doblemente vinculadas en lugar de solo vinculadas) en al menos algunas implementaciones, aquí está la cita de Inada Naoki del rastreador de errores de Python ( problema 33462 ):

"Tener un pedido" no significa "reversible". Por ejemplo, una sola lista vinculada está ordenada, pero no es reversible.

Si bien la implementación de CPython puede proporcionar eficiencia __reverse__, agregar __reverse__significa que se espera que toda la implementación de Python lo proporcione. Por ejemplo, algunas implementaciones de Python pueden implementar dict con hashmap + lista enlazada individual. Si __reverse__se agrega, ya no es posible.

De vuelta a la lista de correo, aquí están los dos últimos mensajes (ambos publicados el 06.06.2018). Primero es de Michael Selik:

¿Estoy en lo cierto al decir que el consenso es +1 para su inclusión en v3.8?

El último punto en el hilo fue INADA Naoki investigando varias implementaciones y decidiendo que está bien incluir esta característica en 3.8. Según tengo entendido, Guido estuvo de acuerdo con el consejo de INADA de esperar la implementación de v3.7 de MicroPython. Dado que INADA ha cambiado de opinión, ¿supongo que todo está a favor?

Concluyendo con el mensaje de Guido van Rossum:

Suena bien para mi. Entonces habremos tenido dos versiones donde este fue el caso:

  • 3.6 donde se implementó la preservación de pedidos en CPython pero en la especificación del lenguaje

  • 3.7 donde también se agregó a la especificación del idioma

Como se señaló en la otra respuesta y comentarios, reversed()es compatible tanto con los dictados como con las vistas desde la versión 3.8 (14.10.2018).

gst
fuente
55
Su segunda cita parece una selección sesgada de ese hilo. El consenso parece ser que la funcionalidad se agregará en 3.8. También antes de Python 3.7 simplemente no había pedidos en un dictobjeto normal (al menos no garantizado por el lenguaje), por lo que reversedtampoco tenía sentido
FlyingTeller
No tengo un perro en la pelea, acabo de citar su primera respuesta. Pero tienes razón, punto bien tomado: eliminé la cita.
gst
1
Gracias. El hilo de discusión del python-dev fue revelador. De hecho, la función ya se implementó en Python 3.8, que se lanzó hace solo dos días (14 de octubre de 2019).
normanius
La cita de los documentos no es útil, solo plantea la siguiente pregunta "entonces, ¿por qué no se implementa el tipo dict __reversed__"? El enlace python-dev tiene contenido útil, pero las partes relevantes deben reproducirse directamente en la respuesta (dado que dichos enlaces externos tienden a pudrirse).
wim
1

Actualización para python 3.8

Dict y dictviews ahora son iterables en orden de inserción invertido usando reverseed ()

>>> dict = {1: "1", 2: "2", 3: "3"}
>>> reversed(dict)
<dict_reversekeyiterator object at 0x7f72ca795130>
Гончаров Дмитрий
fuente
¿Esta respuesta aporta algo nuevo que no esté cubierto por otras respuestas, comentarios o la pregunta en sí?
Normanius
@normanius Tiene una pequeña muestra de código visual, podría ser útil para el navegador rápido
jamylak