¿Cómo modificar las entradas de la lista durante el ciclo for?
178
Ahora sé que no es seguro modificar la lista durante un bucle iterativo. Sin embargo, supongamos que tengo una lista de cadenas y quiero quitar las cadenas. ¿El reemplazo de valores mutables cuenta como modificación?
@ user470379: si los elementos de la lista son mutables no es relevante para saber si es seguro o no modificar la lista en la que se encuentran mientras se repite.
Martineau
Respuestas:
144
Se considera pobre forma. En su lugar, utilice una comprensión de la lista, con asignación de sectores si necesita conservar las referencias existentes a la lista.
La asignación de corte es inteligente y evita modificar el original durante el ciclo, pero requiere la creación de una lista temporal de la longitud del original.
Martineau
11
¿Por qué asignamos b = a?
Vigrond
9
@Vigrond: Entonces, cuando print bse ejecuta la declaración, puede saber si ase modificó en lugar de reemplazarse. Otra posibilidad habría sido print b is aver si ambos todavía se refieren al mismo objeto.
Martineau
12
¿Por qué a [:] = y no solo a =?
kdubs
10
@kdubs: "... con asignación de divisiones si necesita conservar las referencias existentes a la lista".
Ignacio Vazquez-Abrams
162
Dado que el siguiente ciclo solo modifica elementos ya vistos, se consideraría aceptable:
a =['a',' b','c ',' d ']for i, s in enumerate(a):
a[i]= s.strip()print(a)# -> ['a', 'b', 'c', 'd']
Que es diferente de:
a[:]=[s.strip()for s in a]
ya que no requiere la creación de una lista temporal y una asignación de la misma para reemplazar el original, aunque sí requiere más operaciones de indexación.
Precaución: aunque puede modificar las entradas de esta manera, no puede cambiar el número de elementos en el listsin arriesgar la posibilidad de encontrar problemas.
Aquí hay un ejemplo de lo que quiero decir: eliminar una entrada desordena la indexación a partir de ese momento:
b =['a',' b','c ',' d ']for i, s in enumerate(b):if s.strip()!= b[i]:# leading or trailing whitespace?del b[i]print(b)# -> ['a', 'c '] # WRONG!
(El resultado es incorrecto porque no eliminó todos los elementos que debería tener).
Actualizar
Dado que esta es una respuesta bastante popular, aquí se explica cómo eliminar efectivamente las entradas "en el lugar" (aunque esa no es exactamente la pregunta):
b =['a',' b','c ',' d ']
b[:]=[entry for entry in b if entry.strip()== entry]print(b)# -> ['a'] # CORRECT
¿Por qué Python solo hace una copia del elemento individual en la sintaxis for i in a? Esto es muy contradictorio, aparentemente diferente de otros idiomas y ha resultado en errores en mi código que tuve que depurar durante un largo período de tiempo. Python Tutorial ni siquiera lo menciona. Aunque debe haber alguna razón para ello?
xji
1
@JIXiang: No hace copias. Simplemente asigna el nombre de la variable de bucle a elementos sucesivos o al valor de la cosa que se está iterando.
Martineau
1
Eww, ¿por qué usar dos nombres ( a[i]y s) para el mismo objeto en la misma línea cuando no es necesario? Prefiero hacer mucho a[i] = a[i].strip().
Navin
3
@Navin: Porque a[i] = s.strip()solo se realiza una operación de indexación.
Martineau
1
@martineau enumerate(b)realiza una operación de indexación en cada iteración y está haciendo otra con a[i] =. AFAIK es imposible implementar este bucle en Python con solo 1 operación de indexación por iteración del bucle :(
Navin
18
Una variante más para el bucle, me parece más limpia que una con enumerate ():
for idx in range(len(list)):
list[idx]=...# set a new value# some other code which doesn't let you use a list comprehension
Muchos consideran usar algo como range(len(list))en Python un olor a código.
Martineau
2
@Reishin: Dado que enumeratees un generador, no está creando una lista de tuplas, las crea una a la vez a medida que recorre la lista. La única forma de saber cuál es más lenta sería hacerlo timeit.
Martineau
3
El código de @martineau podría no ser muy bonito, pero según timeitenumeratees más lento
Reishin
2
@Reishin: su código de evaluación comparativa no es completamente válido porque no tiene en cuenta la necesidad de recuperar el valor en la lista en el índice dado, que tampoco se muestra en esta respuesta.
martineau
44
@Reishin: Su comparación no es válida precisamente por ese motivo. Mide la sobrecarga de bucle de forma aislada. Para ser concluyente, el tiempo que tarda todo el ciclo en ejecutarse debe medirse debido a la posibilidad de que cualquier diferencia de sobrecarga pueda mitigarse por los beneficios proporcionados al código dentro del ciclo de bucle de una determinada manera; de lo contrario, no está comparando manzanas con manzanas
martineau
11
Está bien modificar cada elemento mientras itera una lista, siempre y cuando no cambie agregar / quitar elementos a la lista.
Puede usar la comprensión de la lista:
l =['a',' list','of ',' string ']
l =[item.strip()for item in l]
o simplemente haz el C-stylebucle for:
for index, item in enumerate(l):
l[index]= item.strip()
No, no alterarías el "contenido" de la lista si pudieras mutar las cadenas de esa manera. Pero en Python no son mutables. Cualquier operación de cadena devuelve una nueva cadena.
Si tuviera una lista de objetos que sabía que eran mutables, podría hacerlo siempre y cuando no cambie el contenido real de la lista.
Por lo tanto, tendrá que hacer un mapa de algún tipo. Si utiliza una expresión generadora, [la operación] se realizará a medida que itera y ahorrará memoria.
No queda claro en su pregunta cuáles son los criterios para decidir qué cadenas eliminar, pero si tiene o puede hacer una lista de las cadenas que desea eliminar, puede hacer lo siguiente:
my_strings =['a','b','c','d','e']
undesirable_strings =['b','d']for undesirable_string in undesirable_strings:for i in range(my_strings.count(undesirable_string)):
my_strings.remove(undesirable_string)
Respuestas:
Se considera pobre forma. En su lugar, utilice una comprensión de la lista, con asignación de sectores si necesita conservar las referencias existentes a la lista.
fuente
print b
se ejecuta la declaración, puede saber sia
se modificó en lugar de reemplazarse. Otra posibilidad habría sidoprint b is a
ver si ambos todavía se refieren al mismo objeto.Dado que el siguiente ciclo solo modifica elementos ya vistos, se consideraría aceptable:
Que es diferente de:
ya que no requiere la creación de una lista temporal y una asignación de la misma para reemplazar el original, aunque sí requiere más operaciones de indexación.
Precaución: aunque puede modificar las entradas de esta manera, no puede cambiar el número de elementos en el
list
sin arriesgar la posibilidad de encontrar problemas.Aquí hay un ejemplo de lo que quiero decir: eliminar una entrada desordena la indexación a partir de ese momento:
(El resultado es incorrecto porque no eliminó todos los elementos que debería tener).
Actualizar
Dado que esta es una respuesta bastante popular, aquí se explica cómo eliminar efectivamente las entradas "en el lugar" (aunque esa no es exactamente la pregunta):
fuente
for i in a
? Esto es muy contradictorio, aparentemente diferente de otros idiomas y ha resultado en errores en mi código que tuve que depurar durante un largo período de tiempo. Python Tutorial ni siquiera lo menciona. Aunque debe haber alguna razón para ello?a[i]
ys
) para el mismo objeto en la misma línea cuando no es necesario? Prefiero hacer muchoa[i] = a[i].strip()
.a[i] = s.strip()
solo se realiza una operación de indexación.enumerate(b)
realiza una operación de indexación en cada iteración y está haciendo otra cona[i] =
. AFAIK es imposible implementar este bucle en Python con solo 1 operación de indexación por iteración del bucle :(Una variante más para el bucle, me parece más limpia que una con enumerate ():
fuente
range(len(list))
en Python un olor a código.enumerate
es un generador, no está creando una lista de tuplas, las crea una a la vez a medida que recorre la lista. La única forma de saber cuál es más lenta sería hacerlotimeit
.timeit
enumerate
es más lentoEstá bien modificar cada elemento mientras itera una lista, siempre y cuando no cambie agregar / quitar elementos a la lista.
Puede usar la comprensión de la lista:
o simplemente haz el
C-style
bucle for:fuente
No, no alterarías el "contenido" de la lista si pudieras mutar las cadenas de esa manera. Pero en Python no son mutables. Cualquier operación de cadena devuelve una nueva cadena.
Si tuviera una lista de objetos que sabía que eran mutables, podría hacerlo siempre y cuando no cambie el contenido real de la lista.
Por lo tanto, tendrá que hacer un mapa de algún tipo. Si utiliza una expresión generadora, [la operación] se realizará a medida que itera y ahorrará memoria.
fuente
Puedes hacer algo como esto:
Se llama comprensión de la lista, para facilitarle el bucle dentro de una lista.
fuente
La respuesta dada por Jemshit Iskenderov e Ignacio Vázquez-Abrams es realmente buena. Se puede ilustrar más con este ejemplo: imagine que
a) Se le da una lista con dos vectores;
b) desea recorrer la lista e invertir el orden de cada una de las matrices
Digamos que tienes
Conseguirás
Por otro lado, si lo haces
El resultado es
fuente
No queda claro en su pregunta cuáles son los criterios para decidir qué cadenas eliminar, pero si tiene o puede hacer una lista de las cadenas que desea eliminar, puede hacer lo siguiente:
que cambia my_strings a ['a', 'c', 'e']
fuente
En resumen, hacer modificaciones en la lista mientras itera la misma lista.
ejemplo:
fuente