¿Por qué la modificación de la variable de iteración no afecta las iteraciones posteriores?

Resuelto firecast asked hace 11 años • 10 respuestas

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 forbucle crea un nuevo alcance para una variable en C, pero no tengo idea sobre Python. ¿Por qué el valor de ino cambia en el forbucle 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, rangese crea un objeto inmutable, por lo que aún así no funcionaría.

firecast avatar Mar 12 '13 20:03 firecast
Aceptado

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 ino 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
Junuxx avatar Mar 12 '2013 13:03 Junuxx

Analogía con el código C

Te estás imaginando que tu for-loopPython 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 ien 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, ise 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.

hughdbrown avatar Mar 12 '2013 14:03 hughdbrown

Un bucle for en Python es en realidad un bucle for-each. Al comienzo de cada bucle, ise establece en el siguiente elemento del iterador ( range(0, 10)en su caso). El valor de ise 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 forbucle 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
Claudiu avatar Mar 12 '2013 13:03 Claudiu

Si por alguna razón realmente desea cambiar agregar 3 a icuando es igual a 5y 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
Jon Clements avatar Mar 12 '2013 14:03 Jon Clements