What does the list [:] in this code mean?

This code is from the Python documentation. I am a bit confused.

words = ['cat', 'window', 'defenestrate'] for w in words[:]: if len(w) > 6: words.insert(0, w) print(words) 

And here is what I thought at first:

 words = ['cat', 'window', 'defenestrate'] for w in words: if len(w) > 6: words.insert(0, w) print(words) 

Why does this code create an infinite loop, but the first does not?

+59
python list iteration for-loop
Jun 19 '17 at 2:53 on
source share
4 answers

This is one of the mistakes! Python that can avoid newbies.

words[:] is a magical sauce here.

Note:

 >>> words = ['cat', 'window', 'defenestrate'] >>> words2 = words[:] >>> words2.insert(0, 'hello') >>> words2 ['hello', 'cat', 'window', 'defenestrate'] >>> words ['cat', 'window', 'defenestrate'] 

And now without [:] :

 >>> words = ['cat', 'window', 'defenestrate'] >>> words2 = words >>> words2.insert(0, 'hello') >>> words2 ['hello', 'cat', 'window', 'defenestrate'] >>> words ['hello', 'cat', 'window', 'defenestrate'] 

The main thing to note here is that words[:] returns copy existing list, so you iterate over a copy that doesn't change.

You can check if you are referencing the same lists using id() :

In the first case:

 >>> words2 = words[:] >>> id(words2) 4360026736 >>> id(words) 4360188992 >>> words2 is words False 

In the second case:

 >>> id(words2) 4360188992 >>> id(words) 4360188992 >>> words2 is words True 

It is worth noting that [i:j] is called the slice operator, and it returns a fresh copy of the list, starting from index i to (but not including) index j .

So words[0:2] give you

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

Skipping the initial index means that it defaults to 0 , and skipping the last index means that it defaults to len(words) , and as a result you get a copy of the whole list.




If you want to make your code a little more readable, I recommend the copy module.

 from copy import copy words = ['cat', 'window', 'defenestrate'] for w in copy(words): if len(w) > 6: words.insert(0, w) print(words) 

This is basically the same as your first code snippet, and much more readable.

Alternatively (as mentioned by DSM in the comments) and in python> = 3, you can also use words.copy() which does the same.

+83
Jun 19 '17 at 15:00
source share

words[:] copies all the elements in words to a new list. Therefore, when you go through words[:] , you actually iterate over all the elements that are currently words . Therefore, when you change words , the effects of these changes do not appear in words[:] (because you called words[:] before you start changing words )

In the last example, you are repeating words , which means that any changes you make to words are really visible to your iterator. As a result, when you insert 0 from words into the index, you β€œtoss” every other element in words one index. Therefore, when you go to the next iteration of your for loop, you will get an element in the next words index, but this is only the element that you just saw (since you inserted the element at the beginning of the list, moving all the other elements up in the index).

To see this in action, try the following code:

 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) 
+11
Jun 19 '17 at 15:05
source share

(In addition to @Coldspeed answer)

Take a look at the examples below:

 words = ['cat', 'window', 'defenestrate'] words2 = words words2 is words 

Results: True

This means that the names word and words2 refer to the same object.

 words = ['cat', 'window', 'defenestrate'] words2 = words[:] words2 is words 

Results: False

In this case, we created a new object.

+5
Jun 19 '17 at 15:04 on
source share

Let's look at the iterator and iteration:

An iterable object that has a __iter__ method that returns an iterator or that defines a __getitem__ method that can take sequential indexes starting from zero (and raises a IndexError when the indexes are no longer valid). Thus, an iterable object that you can get an iterator from.

An iterator is an object with the next (Python 2) or __next__ (Python 3) method.

iter(iterable) returns an iterator object, and list_obj[:] returns a new list object, an exact copy of list_object.

In your first case:

 for w in words[:] 

The for loop will iterate over the new copy of the list, not the original words. Any change in words does not affect the iteration of the loop, and the loop usually ends.

This is how the loop works:

  • the loop calls the iter method to iterate and iterate over the iterator

  • loop calls the next method on the iterator object to get the next element from the iterator. This step is repeated until more items are left.

    Cycle
  • ends when a StopIteration exception StopIteration .

In your second case:

 words = ['cat', 'window', 'defenestrate'] for w in words: if len(w) > 6: words.insert(0, w) print(words) 

You repeat the original words of the list, and adding elements to the words has a direct effect on the iterator object. Therefore, every time your words are updated, the corresponding iterator object is also updated, and therefore creates an infinite loop.

Look at this:

 >>> 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' 

Each time you update the original list to StopIteration , you will get an updated iterator and next respectively. This is why your cycle runs forever.

You can read more about iteration and the iteration protocol here .

+1
Jun 21 '17 at 8:17
source share



All Articles