¿Cuál es el significado de la lista en este código?

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?

Esta es una de las trampas! De python, 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 denomina operador de segmentación , y lo que hace es devolver una copia nueva de la lista a partir del índice i , hasta (pero sin incluir) el índice j .

Entonces, las words[0:2] te dan

 >>> words[0:2] ['hello', 'cat'] 

Omitir el índice de inicio significa que su valor predeterminado es 0 , mientras que omitir el último índice significa que su valor predeterminado es len(words) , y el resultado final es que recibe una copia de toda la lista.


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, esto 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. Por lo tanto, cuando modifica words , los efectos de esas modificaciones no son visibles en las words[:] (porque invocó las words[:] antes de comenzar a modificar las words )

En el último ejemplo, está iterando sobre words , lo que significa que cualquier iterador puede ver cualquier cambio que realice en las words . Como resultado, cuando inserta en el índice 0 de words , “aumenta” cada elemento en words por un índice. Entonces, cuando pase a la siguiente iteración de su bucle 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 principio de la lista, moviendo todo el otro elemento hacia arriba por un índice).

Para ver esto en acción, pruebe 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) 

(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.

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 comenzando desde cero (y genera un IndexError cuando los índices ya no son válidos). Por lo tanto, un objeto 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 el objeto iterador y list_obj[:] devuelve un nuevo objeto list, copia exacta de list_object.

En tu primer caso:

 for w in words[:] 

El bucle for repetirá sobre la nueva copia de la lista, no las palabras originales. Cualquier cambio en las palabras no tiene efecto en la iteración del bucle, y el bucle termina normalmente.

Así es como el bucle hace su trabajo:

  1. bucle llama al método iter en iterable e itera sobre el iterador

  2. el bucle llama al next método en el objeto iterador para obtener el siguiente elemento del iterador. Este paso se repite hasta que no quedan más elementos.

  3. el bucle termina cuando se StopIteration una excepción StopIteration .

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 agregar 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 las next devoluciones en consecuencia. Es por eso que su bucle se ejecuta infinitamente.

Para más información sobre la iteración y el protocolo de iteración, puede consultar aquí .