¿Por qué la modificación de la variable de iteración no afecta las iteraciones posteriores?
Aquí está el código Python con el que tengo problemas:
for i in range (0,10):
if i==5:
i+=3
print i
Esperaba que el resultado 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 for
bucle crea un nuevo alcance para una variable en C, pero no tengo idea sobre Python. ¿Por qué el valor de i
no cambia en el for
bucle en Python y cuál es el remedio para obtener el resultado esperado?
Consulte ¿Cómo modificar las entradas de la lista durante el bucle for? para saber cómo modificar la secuencia original. En 3.x, por supuesto, range
se crea un objeto inmutable, por lo que aún así no funcionaría.
El bucle for itera sobre todos los números en 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 del 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 el código C
Te estás imaginando que tu for-loop
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 desmontaje
Puedes mirar el desmontaje del código 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 pila contiene el rango.
Esto obtiene un iterador sobre el objeto en la parte superior de la pila , es decir, el rango:
15 GET_ITER
En este punto, la parte superior de la pila contiene un iterador sobre el rango realizado.
FOR_ITER comienza a iterar sobre el bucle usando el iterador en la parte superior de la pila:
>> 16 FOR_ITER 36 (to 55)
En este punto, la parte superior de la pila contiene el siguiente valor del iterador.
Y aquí puedes ver que la parte superior de la pila aparece y se asigna ai
:
19 STORE_FAST 0 (i)
Por lo tanto, i
se sobrescribirá independientemente de lo que haga en el bucle.
Aquí hay una descripción general de las máquinas apilables si no la ha visto 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 for
bucle 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 desea cambiar agregar 3 a i
cuando es igual a 5
y omitir los siguientes elementos (esto es como hacer avanzar el puntero en elementos C 3), entonces puede usar un iterador y consumir algunos bits de eso. :
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