Ámbito de la variable python en for loop

Aquí está el código de python que tiene problemas con:

for i in range (0,10): if i==5: i+=3 print i 

Esperaba que la salida fuera:

 0 1 2 3 4 8 9 

sin embargo el intérprete escupe:

 0 1 2 3 4 8 6 7 8 9 

Sé que un bucle for crea un nuevo ámbito para una variable en C, pero no tengo idea acerca de python. ¿Alguien puede explicar por qué el valor de i no cambia en el bucle for en python y cuál es el remedio para obtener el resultado esperado?

El bucle for se repite sobre todos los números en el range(10) , es decir, [0,1,2,3,4,5,6,7,8,9] .
Que cambie el valor actual de i no tiene ningún efecto en el siguiente valor en el rango.

Puede obtener el comportamiento deseado con un bucle while.

 i = 0 while i < 10: # do stuff and manipulate `i` as much as you like if i==5: i+=3 print i # don't forget to increment `i` manually i += 1 

Analogía con código C

Te estás imaginando que tu for-loop en python es como este código C:

 for (int i = 0; i < 10; i++) if (i == 5) i += 3; 

Es más como este código C:

 int r[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (int j = 0; j < sizeof(r)/sizeof(r[0]); j++) { int i = r[j]; if (i == 5) i += 3; } 

Por lo tanto, modificar i en el bucle no tiene el efecto esperado.

Ejemplo de desassembly

Puedes ver el desassembly del código de Python para ver esto:

 >>> from dis import dis >>> def foo(): ... for i in range (0,10): ... if i==5: ... i+=3 ... print i ... >>> dis(foo) 2 0 SETUP_LOOP 53 (to 56) 3 LOAD_GLOBAL 0 (range) 6 LOAD_CONST 1 (0) 9 LOAD_CONST 2 (10) 12 CALL_FUNCTION 2 15 GET_ITER >> 16 FOR_ITER 36 (to 55) 19 STORE_FAST 0 (i) 3 22 LOAD_FAST 0 (i) 25 LOAD_CONST 3 (5) 28 COMPARE_OP 2 (==) 31 POP_JUMP_IF_FALSE 47 4 34 LOAD_FAST 0 (i) 37 LOAD_CONST 4 (3) 40 INPLACE_ADD 41 STORE_FAST 0 (i) 44 JUMP_FORWARD 0 (to 47) 5 >> 47 LOAD_FAST 0 (i) 50 PRINT_ITEM 51 PRINT_NEWLINE 52 JUMP_ABSOLUTE 16 >> 55 POP_BLOCK >> 56 LOAD_CONST 0 (None) 59 RETURN_VALUE >>> 

Esta parte crea un rango entre 0 y 10 y lo realiza:

  3 LOAD_GLOBAL 0 (range) 6 LOAD_CONST 1 (0) 9 LOAD_CONST 2 (10) 12 CALL_FUNCTION 2 

En este punto, la parte superior de la stack contiene el rango.

Esto obtiene un iterador sobre el objeto en la parte superior de la stack , es decir, el rango:

  15 GET_ITER 

En este punto, la parte superior de la stack contiene un iterador sobre el rango realizado.

FOR_ITER comienza a iterar sobre el bucle usando el iterador en la parte superior de la lista:

  >> 16 FOR_ITER 36 (to 55) 

En este punto, la parte superior de la stack contiene el siguiente valor del iterador.

Y aquí puede ver que la parte superior de la stack se abre y se asigna a i :

  19 STORE_FAST 0 (i) 

Así que seré sobrescrito independientemente de lo que haga en el bucle.

Aquí hay una descripción general de las máquinas de stack si no has visto esto antes.

Un bucle for en Python es en realidad un bucle for-each. Al comienzo de cada bucle, i se establece en el siguiente elemento del iterador ( range(0, 10) en su caso). El valor de i se restablece al comienzo de cada bucle, por lo que cambiarlo en el cuerpo del bucle no cambia su valor para la siguiente iteración.

Es decir, el bucle for que escribiste es equivalente al siguiente bucle while:

 _numbers = range(0, 10) #the list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] _iter = iter(_numbers) while True: try: i = _iter.next() except StopIteration: break #--YOUR CODE HERE:-- if i==5: i+=3 print i 

Si, por alguna razón, realmente quería cambiar, agregue 3 a i cuando sea igual a 5 , y omita los siguientes elementos (esto es como avanzar el puntero en los elementos C 3), entonces puede usar un iterador y consumir algunos bits. a partir de ese:

 from collections import deque from itertools import islice x = iter(range(10)) # create iterator over list, so we can skip unnecessary bits for i in x: if i == 5: deque(islice(x, 3), 0) # "swallow up" next 3 items i += 3 # modify current i to be 8 print i 0 1 2 3 4 8 9 

Me reinicio cada iteración, así que realmente no importa lo que le hagas dentro del bucle. La única vez que hace algo es cuando tengo 5, y luego le agrega 3. Una vez que retrocede, vuelve a establecer el siguiente número en la lista. Probablemente quieras usar un while aquí.

El bucle for de Python simplemente recorre la secuencia de valores proporcionada; piense en ello como “foreach”. Por esta razón, la modificación de la variable no tiene efecto en la ejecución del bucle.

Esto está bien descrito en el tutorial .

 it = iter(xrange (0,10)) for i in it: if i==4: all(it.next() for a in xrange(3)) print i 

o

 it = iter(xrange (0,10)) itn = it.next for i in it: if i==4: all(itn() for a in xrange(3)) print i 

En la función de rango de python 2.7, cree una lista, mientras que en las versiones de python 3.x crea un objeto de clase ‘rango’ que solo es iterable, no una lista, similar a xrange en python 2.7.

No cuando estás iterando sobre el rango (1, 10), eventualmente estás leyendo el objeto de tipo de lista e i toma un nuevo valor cada vez que alcanza el bucle.

esto es algo como:

 for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: if i==5: i+=3 print(i) 

Cambiar el valor no cambiará el orden de iteración de la lista.

En mi opinión, el código análogo no es un bucle while, sino un bucle for donde se edita la lista durante el tiempo de ejecución:

 originalLoopRange = 5 loopList = list(range(originalLoopRange)) timesThroughLoop = 0 for loopIndex in loopList: print(timesThroughLoop, "count") if loopIndex == 2: loopList.pop(3) print(loopList) print(loopIndex) timesThroughLoop += 1 

Puedes hacer la siguiente modificación a tu bucle for :

 for i in range (0,10): if i in [5, 6, 7]: continue print(i)