python python-3.x list tuples splat

python - ¿Por qué splatting crea una tupla en el rhs pero una lista en el lhs?



python-3.x tuples (6)

El hecho de que obtenga una tupla en el RHS no tiene nada que ver con el splat. El splat simplemente desempaqueta tu iterador de map . Lo que desempaquetar se decide por el hecho de que ha utilizado la sintaxis de tupla:

*whatever,

en lugar de la sintaxis de la lista:

[*whatever]

o establecer la sintaxis:

{*whatever}

Podrías haber conseguido una lista o un set. Acabas de decirle a Python que haga una tupla.

En el LHS, un objetivo de asignación distribuido siempre produce una lista. No importa si usas "estilo tupla"

*target, = whatever

o "estilo de lista"

[*target] = whatever

sintaxis para la lista de objetivos. La sintaxis se parece mucho a la sintaxis para crear una lista o tupla, pero la sintaxis de la lista de destino es una cosa completamente diferente.

La sintaxis que está utilizando a la izquierda se introdujo en PEP 3132 , para admitir casos de uso como

first, *rest = iterable

En una asignación de desempaquetado, los elementos de un iterable se asignan a objetivos sin estrella por posición, y si hay un objetivo destacado, cualquier extra se rellena en una lista y se asigna a ese objetivo. Se eligió una lista en lugar de una tupla para facilitar el procesamiento adicional . Ya que solo tiene un objetivo destacado en su ejemplo, todos los elementos van en la lista de "extras" asignados a ese objetivo.

Consideremos, por ejemplo,

squares = *map((2).__rpow__, range(5)), squares # (0, 1, 4, 9, 16) *squares, = map((2).__rpow__, range(5)) squares # [0, 1, 4, 9, 16]

Entonces, si todo lo demás es igual, obtenemos una lista cuando salpican las lhs y una tupla cuando se reparten las rs.

¿Por qué?

Es esto por diseño, y si es así, ¿cuál es la razón? O, si no, ¿existen razones técnicas? ¿O es así como es, sin una razón en particular?


Esto se especifica en PEP-0448 desventajas

Mientras que *elements, = iterable hace que los elementos sean una lista, elements = *iterable, hace que los elementos sean una tupla. La razón de esto puede confundir a las personas que no están familiarizadas con la construcción.

También según: especificación PEP-3132

Este PEP propone un cambio a la sintaxis de desempaquetado iterable, lo que permite especificar un nombre "general" al cual se le asignará una lista de todos los elementos no asignados a un nombre "regular".

También mencionado aquí: Python-3 exprlists

Excepto cuando se muestra parte de una lista o conjunto, una lista de expresiones que contiene al menos una coma produce una tupla.
La coma final solo se requiere para crear una sola tupla (también conocida como singleton); Es opcional en todos los demás casos. Una sola expresión sin una coma al final no crea una tupla, sino que produce el valor de esa expresión. (Para crear una tupla vacía, use un par de paréntesis vacíos: ().)

Esto también se puede ver en un ejemplo más simple aquí, donde los elementos de una lista

In [27]: *elements, = range(6) In [28]: elements Out[28]: [0, 1, 2, 3, 4, 5]

Y aquí, donde los elementos son una tupla.

In [13]: elements = *range(6), In [14]: elements Out[14]: (0, 1, 2, 3, 4, 5)

De lo que pude entender a partir de los comentarios y las otras respuestas:

  • El primer comportamiento es mantenerse en línea con las listas de argumentos arbitrarios existentes utilizadas en las funciones, es decir. *args

  • El segundo comportamiento es poder usar las variables en LHS más abajo en la evaluación, por lo que hacer una lista, un valor mutable en lugar de una tupla tiene más sentido


Hay una indicación de la razón por la cual al final de PEP 3132 - Desembalaje Iterable extendido :

Aceptación

Después de una breve discusión sobre la lista de python-3000 [1], el PEP fue aceptado por Guido en su forma actual. Los posibles cambios discutidos fueron:

[...]

Haz que el objetivo destacado sea una tupla en lugar de una lista. Esto sería coherente con los argumentos * de una función, pero dificultaría aún más el procesamiento del resultado.

[1] https://mail.python.org/pipermail/python-3000/2007-May/007198.html

Por lo tanto, la ventaja de tener una lista mutable en lugar de una tupla inmutable parece ser la razón.


No es una respuesta completa, pero el desmontaje da algunas pistas:

from dis import dis def a(): squares = (*map((2).__rpow__, range(5)),) # print(squares) print(dis(a))

desmonta como

5 0 LOAD_GLOBAL 0 (map) 2 LOAD_CONST 1 (2) 4 LOAD_ATTR 1 (__rpow__) 6 LOAD_GLOBAL 2 (range) 8 LOAD_CONST 2 (5) 10 CALL_FUNCTION 1 12 CALL_FUNCTION 2 14 BUILD_TUPLE_UNPACK 1 16 STORE_FAST 0 (squares) 18 LOAD_CONST 0 (None) 20 RETURN_VALUE

mientras

def b(): *squares, = map((2).__rpow__, range(5)) print(dis(b))

resultados en

11 0 LOAD_GLOBAL 0 (map) 2 LOAD_CONST 1 (2) 4 LOAD_ATTR 1 (__rpow__) 6 LOAD_GLOBAL 2 (range) 8 LOAD_CONST 2 (5) 10 CALL_FUNCTION 1 12 CALL_FUNCTION 2 14 UNPACK_EX 0 16 STORE_FAST 0 (squares) 18 LOAD_CONST 0 (None) 20 RETURN_VALUE

El documento en UNPACK_EX establece:

UNPACK_EX (cuentas)

Implementa la asignación con un objetivo destacado: Desempaqueta un iterable en TOS en valores individuales, donde el número total de valores puede ser menor que el número de elementos en iterable: uno de los nuevos valores será una lista de todos los elementos restantes.

El byte bajo de conteos es el número de valores antes del valor de lista, el byte alto de conteos el número de valores después de él. Los valores resultantes se colocan en la pila de derecha a izquierda.

(énfasis mío). mientras que BUILD_TUPLE_UNPACK devuelve una tuple :

BUILD_TUPLE_UNPACK (cuenta)

Pops cuenta iterables de la pila, se une a ellos en una sola tupla y empuja el resultado. Implementa el desempaquetado iterable en las pantallas de tuplas (* x, * y, * z).


Para el RHS, no hay mucho de un problema. La link dice bien:

Lo tenemos funcionando como suele hacer en las llamadas a funciones. Expande los contenidos del iterable al que está adjunto. Entonces, la declaración:

elements = *iterable

se puede ver como:

elements = 1, 2, 3, 4,

que es otra manera de inicializar una tupla.

Ahora, para el LHS, sí, hay razones técnicas para que el LHS use una lista, como se indica en la discusión sobre el PEP 3132 inicial para extender el desempaque

Las razones se pueden deducir de la conversación en el PEP (agregada al final).

Esencialmente se reduce a un par de factores clave:

  • El LHS necesitaba admitir una "expresión destacada" que no estaba necesariamente restringida solo al final.
  • El RHS necesitaba permitir que se aceptaran varios tipos de secuencia, incluidos los iteradores.
  • La combinación de los dos puntos anteriores requirió la manipulación / mutación de los contenidos después de aceptarlos en la expresión destacada.
  • Guido derribó un enfoque alternativo al manejo, uno para imitar al iterador alimentado con RHS, incluso dejando de lado las dificultades de implementación, por su comportamiento inconsistente.
  • Teniendo en cuenta todos los factores anteriores, una tupla en LHS tendría que ser primero una lista y luego convertirse. Este enfoque simplemente agregaría gastos generales y no invitaría a ninguna discusión adicional.

Resumen : Una combinación de varios factores condujo a la decisión de permitir una lista en el LHS y las razones que se comunican entre sí.

Extracto relevante para no permitir tipos inconsistentes:

El caso de uso importante en Python para la semántica propuesta es cuando tiene un registro de longitud variable, los primeros elementos son interesantes y el resto no lo es, pero no carece de importancia. (Si quisiera deshacerse del resto, simplemente escribiría a, b, c = x [: 3] en lugar de a, b, c, * d = x). Es mucho más conveniente para este caso de uso si el tipo de d es fijado por la operación, por lo que puede contar con su comportamiento.

Hay un error en el diseño de filter () en Python 2 (que se solucionará en 3.0 al convertirlo en un iterador BTW): si la entrada es una tupla, la salida también es una tupla, pero si la entrada es una lista O cualquier otra cosa , la salida es una lista. Esa es una firma totalmente insana, ya que significa que no puede contar con que el resultado sea una lista, ni que sea una tupla; si necesita que sea una o la otra, debe convertirla en una, lo cual Es una pérdida de tiempo y espacio. Por favor, no repitamos este error de diseño. -Guido

También he intentado recrear una conversación parcialmente citada que pertenece al resumen anterior. https://mail.python.org/pipermail/python-3000/2007-May/007198.html Énfasis mío.

1.

En las listas de argumentos, * args agota los iteradores, convirtiéndolos en tuplas. Creo que sería confuso si * args en el desempaquetado de tuplas no hiciera lo mismo.

Esto plantea la pregunta de por qué el parche produce listas, no tuplas. ¿Cuál es el razonamiento detrás de eso?

ESTAR

2.

En mi opinión, es probable que desee seguir procesando la secuencia resultante, incluida su modificación.

Georg

3.

Bueno, si eso es a lo que te diriges, entonces espero que sea más útil que el desempaque genere no listas, pero el mismo tipo con el que empezaste, por ejemplo , si empecé con una cadena, probablemente quiero seguir usando cuerdas :: - texto adicional cortado

4.

Cuando se trata de un iterador, no se sabe de antemano la longitud, por lo que la única forma de obtener una tupla sería producir primero una lista y luego crear una tupla a partir de ella. Greg

5.

Sí. Esa fue una de las razones por las que se sugirió que los argumentos * solo deberían aparecer al final del desempaquetado de la tupla.

ESTAR

pareja convos saltó

6.

No creo que devolver el tipo dado sea un objetivo que deba intentarse, porque solo puede funcionar para un conjunto fijo de tipos conocidos. Dado un tipo de secuencia arbitrario, no hay forma de saber cómo crear una nueva instancia de él con contenidos específicos.

- Greg

se han saltado los convos

7.

Estoy sugiriendo que

  • listas de listas de retorno
  • tuplas de retorno tuplas
  • Los contenedores XYZ devuelven los contenedores XYZ
  • iteradores de retorno sin contenedor iterables.

¿Cómo te propones distinguir entre los dos últimos casos? Intentar cortarlo y capturar una excepción no es aceptable , OMI, ya que puede enmascarar errores con demasiada facilidad.

- Greg

8.

Pero espero que sea menos útil. Tampoco admite "a, * b, c =". Desde una POV de implementación , si tiene un objeto desconocido en el RHS, debe intentar cortarlo antes de intentar iterarlo; esto puede causar problemas, por ejemplo, si el objeto es un valor predeterminado, ya que x [3:] se implementa como x [sector (Ninguno, 3, Ninguno)], el valor predeterminado le dará su valor predeterminado. Prefiero definir esto en términos de iteración sobre el objeto hasta que se agote, lo que puede optimizarse para ciertos tipos conocidos como listas y tuplas.

- --Guido van Rossum


TLDR: Obtienes una tuple en el RHS porque pediste una. Obtienes una list en el LHS porque es más fácil.

Es importante tener en cuenta que el RHS se evalúa antes que el LHS, por eso a, b = b, a funciona. La diferencia entonces se hace evidente cuando se divide la asignación y se usan capacidades adicionales para LHS y RHS:

# RHS: Expression List a = head, *tail # LHS: Target List *leading, last = a

En resumen, mientras que los dos parecen similares, son cosas completamente diferentes. RHS es una expresión para crear una tuple de todos los nombres; LHS es un enlace a varios nombres de una tuple . Incluso si ve el LHS como una tupla de nombres, eso no restringe el tipo de cada nombre.

RHS es una lista de expresiones , un literal de tuple sin los paréntesis opcionales () . Esto es lo mismo que 1, 2 crea una tupla incluso sin paréntesis, y cómo encerrar [] o {} crea una list o set . La *tail solo significa desempaquetar en esta tuple .

Nuevo en la versión 3.5: Desembalaje iterable en listas de expresiones, originalmente propuesto por PEP 448 .

El LHS no crea un valor, enlaza valores a varios nombres. Con un nombre completo como *leading , el enlace no se conoce por adelantado en todos los casos. En cambio, el catch-all contiene todo lo que queda.

El uso de una list para almacenar valores hace que esto sea simple: los valores para los nombres finales se pueden eliminar de manera eficiente desde el final. La list restante contiene exactamente los valores para el nombre general. De hecho, esto es exactamente lo que hace CPython :

  • Recoge todos los elementos para objetivos obligatorios antes del protagonista.
  • recoger todos los elementos restantes de lo iterable en una lista
  • Pop elementos para objetivos obligatorios después del destacado de la lista
  • empujar los elementos individuales y la lista de tamaño en la pila

Incluso cuando el LHS tiene un nombre que se engloba sin nombres finales, es una list para la consistencia.