loop - list comprehension python
¿Cuál es el significado de la lista (4)
(Además de la respuesta @Coldspeed)
Mira los siguientes ejemplos:
words = [''cat'', ''window'', ''defenestrate'']
words2 = words
words2 is words
resultados:
True
Significa que los nombres
word
y
words2
refieren al mismo objeto.
words = [''cat'', ''window'', ''defenestrate'']
words2 = words[:]
words2 is words
resultados:
False
En este caso, hemos creado el nuevo objeto.
Esta pregunta ya tiene una respuesta aquí:
Este código es de la documentación de Python. Estoy un poco confundido.
words = [''cat'', ''window'', ''defenestrate'']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
Y lo siguiente es lo que pensé al principio:
words = [''cat'', ''window'', ''defenestrate'']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
¿Por qué este código crea un bucle infinito y el primero no?
Echemos un vistazo a iterador e iterables:
Un iterable es un objeto que tiene un método
__iter__
que devuelve un iterador, o que define un método__getitem__
que puede tomar índices secuenciales a partir de cero (y genera unIndexError
cuando los índices ya no son válidos). Entonces, un iterable es un objeto del que puede obtener un iterador.
Un iterador es un objeto con un método
next
(Python 2) o
__next__
(Python 3).
iter(iterable)
devuelve un objeto iterador, y
list_obj[:]
devuelve un nuevo objeto de lista, copia exacta de list_object.
En tu primer caso:
for w in words[:]
El bucle
for
iterará sobre una nueva copia de la lista, no las palabras originales.
Cualquier cambio en las palabras no tiene efecto en la iteración del ciclo, y el ciclo termina normalmente.
Así es como funciona el bucle:
-
loop llama al método
iter
en iterable e itera sobre el iterador -
loop llama al
next
método en el objeto iterador para obtener el siguiente elemento del iterador. Este paso se repite hasta que no queden más elementos. -
el ciclo termina cuando se
StopIteration
una excepciónStopIteration
.
En tu segundo caso:
words = [''cat'', ''window'', ''defenestrate'']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
Está iterando sobre las palabras de la lista original y agregando elementos a las palabras tiene un impacto directo en el objeto iterador. Por lo tanto, cada vez que se actualizan sus palabras, el objeto iterador correspondiente también se actualiza y, por lo tanto, crea un bucle infinito.
Mira esto:
>>> l = [2, 4, 6, 8]
>>> i = iter(l) # returns list_iterator object which has next method
>>> next(i)
2
>>> next(i)
4
>>> l.insert(2, ''A'')
>>> next(i)
''A''
Cada vez que actualice su lista original antes de
StopIteration
, obtendrá el iterador actualizado y el
next
regresará en consecuencia.
Es por eso que tu ciclo se ejecuta infinitamente.
Para obtener más información sobre la iteración y el protocolo de iteración, puede consultar here .
Esta es una de las trampas! de pitón, que puede escapar de los principiantes.
Las
words[:]
es la salsa mágica aquí.
Observar:
>>> words = [''cat'', ''window'', ''defenestrate'']
>>> words2 = words[:]
>>> words2.insert(0, ''hello'')
>>> words2
[''hello'', ''cat'', ''window'', ''defenestrate'']
>>> words
[''cat'', ''window'', ''defenestrate'']
Y ahora sin el
[:]
:
>>> words = [''cat'', ''window'', ''defenestrate'']
>>> words2 = words
>>> words2.insert(0, ''hello'')
>>> words2
[''hello'', ''cat'', ''window'', ''defenestrate'']
>>> words
[''hello'', ''cat'', ''window'', ''defenestrate'']
Lo principal a tener en cuenta aquí es que las
words[:]
devuelven una
copy
de la lista existente, por lo que está iterando sobre una copia, que no se modifica.
Puede verificar si se está refiriendo a las mismas listas usando
id()
:
En el primer caso:
>>> words2 = words[:]
>>> id(words2)
4360026736
>>> id(words)
4360188992
>>> words2 is words
False
En el segundo caso:
>>> id(words2)
4360188992
>>> id(words)
4360188992
>>> words2 is words
True
Vale la pena señalar que
[i:j]
se llama
operador de corte
, y lo que hace es devolver una copia nueva de la lista comenzando desde el índice
i
, hasta el índice
j
(pero sin incluirlo).
Entonces, las
words[0:2]
te dan
>>> words[0:2]
[''hello'', ''cat'']
Omitir el índice inicial significa que el valor predeterminado es
0
, mientras que omitir el último índice significa que el valor predeterminado es
len(words)
, y el resultado final es que recibe una copia de la lista
completa
.
Si desea que su código sea un poco más legible, le recomiendo el módulo de
copy
.
from copy import copy
words = [''cat'', ''window'', ''defenestrate'']
for w in copy(words):
if len(w) > 6:
words.insert(0, w)
print(words)
Básicamente, hace lo mismo que su primer fragmento de código, y es mucho más legible.
Alternativamente (como lo menciona DSM en los comentarios) y en python> = 3, también puede usar
words.copy()
que hace lo mismo.
words[:]
copia todos los elementos en
words
en una nueva lista.
Entonces, cuando iteras sobre las
words[:]
, en realidad estás iterando sobre todos los elementos que las
words
actualmente.
Entonces, cuando modifica
words
, los efectos de esas modificaciones no son visibles en las
words[:]
(porque invocó
words[:]
antes de comenzar a modificar las
words
)
En el último ejemplo, está iterando sobre
words
, lo que significa que cualquier cambio que realice en las
words
es visible para su iterador.
Como resultado, cuando inserta en el índice 0 de
words
, "sube" todos los demás elementos en
words
por un índice.
Entonces, cuando pase a la siguiente iteración de su ciclo for, obtendrá el elemento en el siguiente índice de
words
, pero ese es solo el elemento que acaba de ver (porque insertó un elemento al comienzo de la lista, mover todo el otro elemento hacia arriba por un índice).
Para ver esto en acción, intente el siguiente código:
words = [''cat'', ''window'', ''defenestrate'']
for w in words:
print("The list is:", words)
print("I am looking at this word:", w)
if len(w) > 6:
print("inserting", w)
words.insert(0, w)
print("the list now looks like this:", words)
print(words)