python for-loop indexing

python - ¿Por qué puedo usar un índice de lista como variable de indexación en un bucle for?



for-loop indexing (6)

(Este es más un comentario largo que una respuesta: ya hay un par de buenos, especialmente los de @TrebledJ''s . Pero tuve que pensarlo explícitamente en términos de sobrescribir variables que ya tienen valores antes de que hiciera clic en él).

Si tuvieras

x = 0 l = [1, 2, 3] for x in l: print(x)

no se sorprendería de que x se sobrescriba cada vez que pasa por el bucle. A pesar de que x existió antes, su valor no se usa (es decir, for 0 in l: lo que arrojaría un error). Más bien, asignamos los valores de l a x .

Cuando hacemos

a = [0, 1, 2, 3] for a[-1] in a: print(a[-1])

Aunque a[-1] ya existe y tiene un valor, no lo asignamos, sino que lo asignamos a a[-1] cada vez que pasa por el bucle.

Tengo el siguiente código:

a = [0,1,2,3] for a[-1] in a: print(a[-1])

La salida es:

0 1 2 2

Estoy confundido acerca de por qué un índice de lista se puede usar como una variable de indexación en un bucle for.


Es una pregunta interesante, y puedes entenderla por eso:

for v in a: a[-1] = v print(a[-1]) print(a)

en realidad se convierte en: [0, 1, 2, 2] después del bucle

Salida:

0 1 2 2 [0, 1, 2, 2]

Espero que eso te ayude, y comentar si tienes más preguntas. :)


La respuesta de TrebledJ explica la razón técnica de por qué esto es posible.

¿Por qué querrías hacer esto?

Supongamos que tengo un algoritmo que opera en una matriz:

x = np.arange(5)

Y quiero probar el resultado del algoritmo usando diferentes valores del primer índice. Simplemente puedo omitir el primer valor, reconstruyendo una matriz cada vez:

for i in range(5): print(np.r_[i, x[1:]].sum())

( np.r_ )

Esto creará una nueva matriz en cada iteración, lo cual no es ideal, en particular si la matriz es grande. Para reutilizar la misma memoria en cada iteración, puedo reescribirla como:

for i in range(5): x[0] = i print(x.sum())

Lo que probablemente sea más claro que la primera versión también.

Pero eso es exactamente idéntico a la forma más compacta de escribir esto:

for x[0] in range(5): print(x.sum())

Todo lo anterior resultará en:

10 11 12 13 14

Ahora, este es un "algoritmo" trivial, pero habrá propósitos más complicados donde uno podría querer probar el cambio de un valor único (o múltiple, pero que complica las cosas debido al desempaquetado de la asignación) en una matriz a un número de valores, preferiblemente sin copiando toda la matriz. En este caso, es posible que desee utilizar un valor indexado en un bucle, pero esté preparado para confundir a cualquiera que mantenga su código (incluido usted mismo). Por esta razón, la segunda versión que asigna explícitamente x[0] = i es probablemente preferible, pero si prefiere el más compacto for x[0] in range(5) , este debería ser un caso de uso válido.


La expresión izquierda de una instrucción de bucle for se asigna con cada elemento en el iterable a la derecha en cada iteración, por lo que

for n in a: print(n)

es sólo una manera elegante de hacer:

for i in range(len(a)): n = a[i] print(n)

Igualmente,

for a[-1] in a: print(a[-1])

es sólo una manera elegante de hacer:

for i in range(len(a)): a[-1] = a[i] print(a[-1])

donde en cada iteración, el último elemento de a se asigna con el siguiente elemento en a , de modo que cuando la iteración llega finalmente al último elemento, su valor se asignó por última vez con el segundo último elemento, 2 .


Los índices de la lista como a[-1] en la expresión for a[-1] in a son válidos según lo especificado por el for_stmt (y específicamente la lista target_list ), donde el segmentado es un objetivo válido para la asignación.

"¿Eh? ¿Asignación? ¿Qué tiene que ver eso con mi salida?"

De hecho, tiene todo que ver con la salida y el resultado. Vamos a profundizar en la documentación para un bucle for-in :

for_stmt ::= "for" target_list "in" expression_list ":" suite

La lista de expresiones se evalúa una vez; debe producir un objeto iterable. Se crea un iterador para el resultado de la expression_list . La suite se ejecuta una vez para cada elemento proporcionado por el iterador, en el orden devuelto por el iterador. Cada elemento, a su vez, se asigna a la lista de destino mediante las reglas estándar para las asignaciones (consulte los enunciados de asignación ), y luego se ejecuta el conjunto.

(énfasis añadido)
NB: la suite se refiere a la (s) declaración (es) debajo de for-block, print(a[-1]) en nuestro caso particular.

Vamos a divertirnos un poco y extender la declaración de impresión:

a = [0, 1, 2, 3] for a[-1] in a: print(a, a[-1])

Esto da el siguiente resultado:

[0, 1, 2, 0] 0 # a[-1] assigned 0 [0, 1, 2, 1] 1 # a[-1] assigned 1 [0, 1, 2, 2] 2 # a[-1] assigned 2 [0, 1, 2, 2] 2 # a[-1] assigned 2 (itself)

(comentarios añadidos)

Aquí, a[-1] cambia en cada iteración y vemos que este cambio se propaga a a . Nuevamente, esto es posible debido a que el slicing es un objetivo válido.

Un buen argumento hecho por ev. Kounis considera la primera oración del documento citado más arriba: " La lista de expresiones se evalúa una vez ". ¿Esto no implica que la lista de expresiones sea estática e inmutable, constante en [0, 1, 2, 3] ? ¿No debería asignarse a[-1] 3 en la iteración final?

Bueno, Konrad Rudolph afirma que:

No, [la lista de expresiones se evalúa] una vez para crear un objeto iterable . Pero ese objeto iterable todavía itera sobre los datos originales , no una copia de ellos.

(énfasis añadido)

El siguiente código demuestra cómo un iterable it perezily produce elementos de una lista x .

x = [1, 2, 3, 4] it = iter(x) print(next(it)) # 1 print(next(it)) # 2 print(next(it)) # 3 x[-1] = 0 print(next(it)) # 0

( código inspirado en Kounis'' )

Si la evaluación fuera impaciente, podríamos esperar que x[-1] = 0 tenga un efecto cero y esperar que se imprima 4. Esto claramente no es el caso y sirve para demostrar que, con el mismo principio, nuestro -loop produce perezosamente los números de las siguientes asignaciones a a[-1] en cada iteración.


a[-1] refiere al último elemento de a , en este caso a[3] . El bucle for es un poco inusual porque utiliza este elemento como variable de bucle. No está evaluando ese elemento tras la entrada del bucle, sino que lo está asignando en cada iteración a través del bucle.

Entonces, primero a[-1] se establece en 0, luego 1, luego 2. Finalmente, en la última iteración, el bucle for recupera a[3] que en ese punto es 2 , por lo que la lista termina como [0, 1, 2, 2] .

Un bucle más típico for bucle usa un nombre de variable local simple como la variable del bucle, por ejemplo, for x ... En ese caso, x se establece en el siguiente valor en cada iteración. Este caso no es diferente, excepto que a[-1] se establece en el siguiente valor en cada iteración. No ves esto muy a menudo, pero es consistente.