Python Language Cambiando la secuencia sobre la que estás iterando


Ejemplo

Un bucle for repite en una secuencia, por lo que alterar esta secuencia dentro del bucle podría generar resultados inesperados (especialmente al agregar o eliminar elementos):

alist = [0, 1, 2]
for index, value in enumerate(alist):
    alist.pop(index)
print(alist)
# Out: [1]

Nota: list.pop() se está utilizando para eliminar elementos de la lista.

El segundo elemento no se eliminó porque la iteración pasa por los índices en orden. El bucle anterior se repite dos veces, con los siguientes resultados:

# Iteration #1
index = 0
alist = [0, 1, 2]
alist.pop(0) # removes '0'

# Iteration #2
index = 1
alist = [1, 2]
alist.pop(1) # removes '2'

# loop terminates, but alist is not empty:
alist = [1]

Este problema surge porque los índices cambian mientras se iteran en la dirección del índice creciente. Para evitar este problema, puede recorrer el bucle hacia atrás :

alist = [1,2,3,4,5,6,7]
for index, item in reversed(list(enumerate(alist))):
    # delete all even items
    if item % 2 == 0:
        alist.pop(index)
print(alist)
# Out: [1, 3, 5, 7]

Al recorrer el bucle que comienza al final, a medida que se eliminan (o agregan) los elementos, no afecta a los índices de los elementos que aparecen anteriormente en la lista. Por lo tanto, este ejemplo eliminará correctamente todos los elementos que sean pares de alist .


Un problema similar surge cuando se insertan o agregan elementos a una lista sobre la que está iterando , lo que puede dar lugar a un bucle infinito:

alist = [0, 1, 2]
for index, value in enumerate(alist):
    # break to avoid infinite loop:
    if index == 20:     
        break           
    alist.insert(index, 'a')
print(alist)
# Out (abbreviated): ['a', 'a', ..., 'a', 'a',  0,   1,   2]

Sin la condición de break , el bucle insertaría 'a' siempre que la computadora no se quede sin memoria y el programa pueda continuar. En una situación como esta, generalmente se prefiere crear una nueva lista y agregar elementos a la nueva lista a medida que recorre la lista original.


Cuando se utiliza un bucle for , no puede modificar los elementos de la lista con la variable de marcador de posición :

alist = [1,2,3,4]
for item in alist:
    if item % 2 == 0:
        item = 'even'
print(alist)
# Out: [1,2,3,4]

En el ejemplo anterior, cambiar el item no cambia realmente nada en la lista original . Debe usar el índice de alist[2] ( alist[2] ), y enumerate() funciona bien para esto:

alist = [1,2,3,4]
for index, item in enumerate(alist):
    if item % 2 == 0:
        alist[index] = 'even'
print(alist)
# Out: [1, 'even', 3, 'even']

Un while de bucle podría ser una mejor elección en algunos casos:

Si va a eliminar todos los elementos de la lista:

zlist = [0, 1, 2]
while zlist:
    print(zlist[0])
    zlist.pop(0)
print('After: zlist =', zlist)

# Out: 0
#      1
#      2
# After: zlist = []

Aunque simplemente restablecer zlist logrará el mismo resultado;

zlist = []

El ejemplo anterior también se puede combinar con len() para detenerse después de un cierto punto, o para eliminar todos los elementos excepto x en la lista:

zlist = [0, 1, 2]
x = 1
while len(zlist) > x:
    print(zlist[0])
    zlist.pop(0)
print('After: zlist =', zlist)

# Out: 0
#      1
# After: zlist = [2]

O para recorrer una lista mientras elimina elementos que cumplen una determinada condición (en este caso, eliminar todos los elementos pares):

zlist = [1,2,3,4,5]
i = 0
while i < len(zlist):
    if zlist[i] % 2 == 0:
        zlist.pop(i)
    else:
        i += 1
print(zlist)
# Out: [1, 3, 5]

Observe que no incrementa i después de eliminar un elemento. Al eliminar el elemento en zlist[i] , el índice del siguiente elemento ha disminuido en uno, por lo que al marcar zlist[i] con el mismo valor para i en la siguiente iteración, estará verificando correctamente el siguiente elemento en la lista .


Una forma contraria de pensar en eliminar elementos no deseados de una lista, es agregar elementos deseados a una nueva lista . El ejemplo siguiente es una alternativa a este último while ejemplo de bucle:

zlist = [1,2,3,4,5]

z_temp = []
for item in zlist:
    if item % 2 != 0:
        z_temp.append(item)
zlist = z_temp
print(zlist)
# Out: [1, 3, 5]

Aquí estamos canalizando los resultados deseados en una nueva lista. De manera opcional, podemos reasignar la lista temporal a la variable original.

Con esta tendencia de pensamiento, puede invocar una de las funciones más elegantes y potentes de Python, listas de comprensión , que elimina las listas temporales y se desvía de la ideología de mutación de listas / índices in situ anteriormente discutida.

zlist = [1,2,3,4,5]
[item for item in zlist if item % 2 != 0]
# Out: [1, 3, 5]