Desembalaje, desembalaje extendido y desembalaje extendido anidado

Resuelto treecoder asked hace 13 años • 4 respuestas

Considere las siguientes expresiones. Tenga en cuenta que algunas expresiones se repiten para presentar el "contexto".

(esta es una lista larga)

a, b = 1, 2                          # simple sequence assignment
a, b = ['green', 'blue']             # list asqignment
a, b = 'XY'                          # string assignment
a, b = range(1,5,2)                  # any iterable will do


                                     # nested sequence assignment

(a,b), c = "XY", "Z"                 # a = 'X', b = 'Y', c = 'Z' 

(a,b), c = "XYZ"                     # ERROR -- too many values to unpack
(a,b), c = "XY"                      # ERROR -- need more than 1 value to unpack

(a,b), c, = [1,2],'this'             # a = '1', b = '2', c = 'this'
(a,b), (c,) = [1,2],'this'           # ERROR -- too many values to unpack


                                     # extended sequence unpacking

a, *b = 1,2,3,4,5                    # a = 1, b = [2,3,4,5]
*a, b = 1,2,3,4,5                    # a = [1,2,3,4], b = 5
a, *b, c = 1,2,3,4,5                 # a = 1, b = [2,3,4], c = 5

a, *b = 'X'                          # a = 'X', b = []
*a, b = 'X'                          # a = [], b = 'X'
a, *b, c = "XY"                      # a = 'X', b = [], c = 'Y'
a, *b, c = "X...Y"                   # a = 'X', b = ['.','.','.'], c = 'Y'

a, b, *c = 1,2,3                     # a = 1, b = 2, c = [3]
a, b, c, *d = 1,2,3                  # a = 1, b = 2, c = 3, d = []

a, *b, c, *d = 1,2,3,4,5             # ERROR -- two starred expressions in assignment

(a,b), c = [1,2],'this'              # a = '1', b = '2', c = 'this'
(a,b), *c = [1,2],'this'             # a = '1', b = '2', c = ['this']

(a,b), c, *d = [1,2],'this'          # a = '1', b = '2', c = 'this', d = []
(a,b), *c, d = [1,2],'this'          # a = '1', b = '2', c = [], d = 'this'

(a,b), (c, *d) = [1,2],'this'        # a = '1', b = '2', c = 't', d = ['h', 'i', 's']

*a = 1                               # ERROR -- target must be in a list or tuple
*a = (1,2)                           # ERROR -- target must be in a list or tuple
*a, = (1,2)                          # a = [1,2]
*a, = 1                              # ERROR -- 'int' object is not iterable
*a, = [1]                            # a = [1]
*a = [1]                             # ERROR -- target must be in a list or tuple
*a, = (1,)                           # a = [1]
*a, = (1)                            # ERROR -- 'int' object is not iterable

*a, b = [1]                          # a = [], b = 1
*a, b = (1,)                         # a = [], b = 1

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
(a,b), *c = 1,2,3                    # ERROR - 'int' object is not iterable
(a,b), *c = 'XY', 2, 3               # a = 'X', b = 'Y', c = [2,3]


                                     # extended sequence unpacking -- NESTED

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
*(a,b), c = 1,2,3                    # a = 1, b = 2, c = 3

*(a,b) = 1,2                         # ERROR -- target must be in a list or tuple
*(a,b), = 1,2                        # a = 1, b = 2

*(a,b) = 'XY'                        # ERROR -- target must be in a list or tuple
*(a,b), = 'XY'                       # a = 'X', b = 'Y'

*(a, b) = 'this'                     # ERROR -- target must be in a list or tuple
*(a, b), = 'this'                    # ERROR -- too many values to unpack
*(a, *b), = 'this'                   # a = 't', b = ['h', 'i', 's']

*(a, *b), c = 'this'                 # a = 't', b = ['h', 'i'], c = 's'

*(a,*b), = 1,2,3,3,4,5,6,7           # a = 1, b = [2, 3, 3, 4, 5, 6, 7]

*(a,*b), *c = 1,2,3,3,4,5,6,7        # ERROR -- two starred expressions in assignment
*(a,*b), (*c,) = 1,2,3,3,4,5,6,7     # ERROR -- 'int' object is not iterable
*(a,*b), c = 1,2,3,3,4,5,6,7         # a = 1, b = [2, 3, 3, 4, 5, 6], c = 7
*(a,*b), (*c,) = 1,2,3,4,5,'XY'      # a = 1, b = [2, 3, 4, 5], c = ['X', 'Y']

*(a,*b), c, d = 1,2,3,3,4,5,6,7      # a = 1, b = [2, 3, 3, 4, 5], c = 6, d = 7
*(a,*b), (c, d) = 1,2,3,3,4,5,6,7    # ERROR -- 'int' object is not iterable
*(a,*b), (*c, d) = 1,2,3,3,4,5,6,7   # ERROR -- 'int' object is not iterable
*(a,*b), *(c, d) = 1,2,3,3,4,5,6,7   # ERROR -- two starred expressions in assignment


*(a,b), c = 'XY', 3                  # ERROR -- need more than 1 value to unpack
*(*a,b), c = 'XY', 3                 # a = [], b = 'XY', c = 3
(a,b), c = 'XY', 3                   # a = 'X', b = 'Y', c = 3

*(a,b), c = 'XY', 3, 4               # a = 'XY', b = 3, c = 4
*(*a,b), c = 'XY', 3, 4              # a = ['XY'], b = 3, c = 4
(a,b), c = 'XY', 3, 4                # ERROR -- too many values to unpack

¿Cómo deducir correctamente el resultado de tales expresiones a mano?

treecoder avatar Aug 06 '11 21:08 treecoder
Aceptado

Mis disculpas por la extensión de esta publicación, pero decidí optar por estar completa.

Una vez que conozca algunas reglas básicas, no será difícil generalizarlas. Haré todo lo posible para explicarlo con algunos ejemplos. Ya que estás hablando de evaluarlos "a mano", sugeriré algunas reglas de sustitución simples. Básicamente, puede resultarle más fácil comprender una expresión si todos los iterables tienen el mismo formato.

Solo para fines de desempaquetado, las siguientes sustituciones son válidas en el lado derecho de =(es decir, para rvalues ):

'XY' -> ('X', 'Y')
['X', 'Y'] -> ('X', 'Y')

Si descubre que un valor no se descomprime, deshará la sustitución. (Consulte a continuación para obtener más explicaciones).

Además, cuando vea comas "desnudas", haga como si hubiera una tupla de nivel superior. Haga esto tanto en el lado izquierdo como en el derecho (es decir, para lvalues ​​y rvalues ):

'X', 'Y' -> ('X', 'Y')
a, b -> (a, b)

Con esas simples reglas en mente, aquí hay algunos ejemplos:

(a,b), c = "XY", "Z"                 # a = 'X', b = 'Y', c = 'Z'

Aplicando las reglas anteriores, convertimos "XY"a ('X', 'Y')y cubrimos las comas desnudas entre paréntesis:

((a, b), c) = (('X', 'Y'), 'Z')

La correspondencia visual aquí hace que sea bastante obvio cómo funciona la tarea.

He aquí un ejemplo erróneo:

(a,b), c = "XYZ"

Siguiendo las reglas de sustitución anteriores, obtenemos lo siguiente:

((a, b), c) = ('X', 'Y', 'Z')

Esto es claramente erróneo; las estructuras anidadas no coinciden. Ahora veamos cómo funciona con un ejemplo un poco más complejo:

(a,b), c, = [1,2],'this'             # a = '1', b = '2', c = 'this'

Aplicando las reglas anteriores, obtenemos

((a, b), c) = ((1, 2), ('t', 'h', 'i', 's'))

Pero ahora queda claro en la estructura que 'this'no se descomprimirá, sino que se asignará directamente a c. Entonces deshacemos la sustitución.

((a, b), c) = ((1, 2), 'this')

Ahora veamos qué sucede cuando envolvemos cuna tupla:

(a,b), (c,) = [1,2],'this'           # ERROR -- too many values to unpack

se convierte

((a, b), (c,)) = ((1, 2), ('t', 'h', 'i', 's'))

Nuevamente el error es obvio. cYa no es una variable simple, sino una variable dentro de una secuencia, por lo que la secuencia correspondiente a la derecha se descomprime en (c,). Pero las secuencias tienen una longitud diferente, por lo que hay un error.

Ahora para un desembalaje extendido usando el *operador. Esto es un poco más complejo, pero sigue siendo bastante sencillo. Una variable precedida por *se convierte en una lista que contiene todos los elementos de la secuencia correspondiente que no están asignados a nombres de variables. Comenzando con un ejemplo bastante simple:

a, *b, c = "X...Y"                   # a = 'X', b = ['.','.','.'], c = 'Y'

esto se convierte

(a, *b, c) = ('X', '.', '.', '.', 'Y')

La forma más sencilla de analizar esto es trabajar desde los extremos. 'X'está asignado a ay 'Y'está asignado a c. Los valores restantes de la secuencia se colocan en una lista y se asignan a b.

Los valores L como (*a, b)y (a, *b)son solo casos especiales de lo anterior. No puedes tener dos *operadores dentro de una secuencia lvalue porque sería ambiguo. ¿Dónde irían los valores en algo como esto (a, *b, *c, d): en bo c? Consideraré el caso anidado en un momento.

*a = 1                               # ERROR -- target must be in a list or tuple

Aquí el error se explica por sí solo. El objetivo ( *a) debe estar en una tupla.

*a, = (1,2)                          # a = [1,2]

Esto funciona porque hay una coma desnuda. Aplicando las reglas...

(*a,) = (1, 2)

Como no hay más variables que *a, *asorbe todos los valores en la secuencia rvalue. ¿Qué pasa si reemplazas el (1, 2)con un valor único?

*a, = 1                              # ERROR -- 'int' object is not iterable

se convierte

(*a,) = 1

Una vez más, el error aquí se explica por sí mismo. No puedes descomprimir algo que no sea una secuencia y *anecesitas algo para descomprimir. Entonces lo ponemos en una secuencia.

*a, = [1]                            # a = [1]

que es equivalente a

(*a,) = (1,)

Finalmente, este es un punto común de confusión: (1)es lo mismo que 1: necesitas una coma para distinguir una tupla de una declaración aritmética.

*a, = (1)                            # ERROR -- 'int' object is not 

Ahora para anidar. En realidad, este ejemplo no estaba en la sección "NESTED"; ¿Quizás no te diste cuenta de que estaba anidado?

(a,b), *c = 'XY', 2, 3               # a = 'X', b = 'Y', c = [2,3]

se convierte

((a, b), *c) = (('X', 'Y'), 2, 3)

Se asigna el primer valor de la tupla de nivel superior y los valores restantes de la tupla de nivel superior ( 2y 3) se asignan a c, tal como deberíamos esperar.

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
*(a,b), c = 1,2,3                    # a = 1, b = 2, c = 3

Ya expliqué anteriormente por qué la primera línea arroja un error. La segunda línea es tonta, pero he aquí por qué funciona:

(*(a, b), c) = (1, 2, 3)

Como hemos explicado anteriormente, trabajamos desde los extremos. 3se asigna a cy luego los valores restantes se asignan a la variable *precedida por ella, en este caso, (a, b). Eso es equivalente a (a, b) = (1, 2), lo que funciona porque hay la cantidad correcta de elementos. No se me ocurre ninguna razón por la que esto aparezca alguna vez en el código de trabajo. Similarmente,

*(a, *b), c = 'this'                 # a = 't', b = ['h', 'i'], c = 's'

se convierte

(*(a, *b), c) = ('t', 'h', 'i', 's')

Trabajando desde los extremos, 's'está asignado a cy ('t', 'h', 'i')está asignado a (a, *b). Trabajando nuevamente desde los extremos, 't'se asigna a ay ('h', 'i')se asigna a b como lista. Este es otro ejemplo tonto que nunca debería aparecer en el código de trabajo.

senderle avatar Aug 06 '2011 17:08 senderle

El descompresión de la tupla de Python 2 me parece bastante sencillo. Cada nombre de la izquierda se corresponde con una secuencia completa o con un solo elemento de una secuencia de la derecha. Si los nombres corresponden a elementos individuales de cualquier secuencia, entonces debe haber suficientes nombres para cubrir todos los elementos.

Sin embargo, el desembalaje prolongado puede resultar ciertamente confuso, porque es muy potente. La realidad es que nunca deberías hacer los últimos 10 o más ejemplos válidos que diste; si los datos están tan estructurados, deberían estar en una dictinstancia de clase o en una, no en formas no estructuradas como listas.

Claramente, se puede abusar de la nueva sintaxis. La respuesta a su pregunta es que no debería tener que leer expresiones como esa; son una mala práctica y dudo que se utilicen.

El hecho de que puedas escribir expresiones arbitrariamente complejas no significa que debas hacerlo. Podrías escribir código como map(map, iterable_of_transformations, map(map, iterable_of_transformations, iterable_of_iterables_of_iterables))pero no lo haces .

agf avatar Aug 06 '2011 15:08 agf

Si cree que su código puede ser engañoso, utilice otra forma para expresarlo.

Es como usar corchetes adicionales en las expresiones para evitar preguntas sobre la precedencia de los operadores. Siempre es una buena inversión hacer que su código sea legible.

Prefiero usar el desempaquetado solo para tareas simples como el intercambio.

Michał Šrajer avatar Aug 06 '2011 15:08 Michał Šrajer

La idea original de la expresión con asterisco en el lado izquierdo es mejorar la legibilidad del desempaquetado iterable como se muestra a continuación:

first_param, rest_param, third_param = param[0], param[1:-1], param[-1]

Esta afirmación equivale a

first_param, *rest_param, third_param = param

En la declaración anterior, la expresión destacada se utiliza para "captar" todos los elementos que no están asignados a "objetivos obligatorios" ( first_paramy third_paramen este ejemplo).

El uso de la expresión con asterisco en el lado izquierdo tiene las siguientes reglas:

  1. como máximo una expresión destacada a la izquierda, o el desembalaje no será único
*a,b,*c = range(5) # wrong
*a,b,c = range(5) # right
a,*b,c = range(5) # right
  1. Para recopilar elementos de 'resto', se debe utilizar la expresión destacada con objetivos obligatorios. La coma final se utiliza para indicar que los objetivos obligatorios no existen.
*a = range(5) # wrong
*a, = range(5) # right

Creo que si dominas estas dos reglas, podrás deducir cualquier resultado de la expresión destacada en el lado izquierdo.

Karuse Tang avatar Apr 13 '2021 12:04 Karuse Tang