This question already has an answer here:
This code is from Python's Documentation. I'm a little confused.
words = ['cat', 'window', 'defenestrate']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
And the following 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 a infinite loop and the first one doesn't?
Let's have a look at iterator and iterables:
An iterator is an object with a
next
(Python 2) or__next__
(Python 3) method.iter(iterable)
returns iterator object, andlist_obj[:]
returns a new list object, exact copy of list_object.In your first case:
The
for
loop will iterate over new copy of the list not the original words. Any change in words has no effect on loop iteration, and the loop terminates normally.This is how the loop does its work:
loop calls
iter
method on iterable and iterates over the iteratorloop calls
next
method on iterator object to get next item from iterator. This step is repeated until there are no more elements leftloop terminates when a
StopIteration
exception is raised.In your second case:
You are iterating over the original list words and adding elements to words have a direct impact on the iterator object. So every time your words is updated, the corresponding iterator object is also updated and therefore creates an infinite loop.
Look at this:
Every time you update your original list before
StopIteration
you will get the updated iterator andnext
returns accordingly. That's why your loop runs infinitely.For more on iteration and the iteration protocol you can look here.
This is one of the gotchas! of python, that can escape beginners.
The
words[:]
is the magic sauce here.Observe:
And now without the
[:]
:The main thing to note here is that
words[:]
returns acopy
of the existing list, so you are iterating over a copy, which is not modified.You can check whether you are referring to the same lists using
id()
:In the first case:
In the second case:
It is worth noting that
[i:j]
is called the slicing operator, and what it does is it returns a fresh copy of the list starting from indexi
, upto (but not including) indexj
.So,
words[0:2]
gives youOmitting the starting index means it defaults to
0
, while omitting the last index means it defaults tolen(words)
, and the end result is that you receive a copy of the entire list.If you want to make your code a little more readable, I recommend the
copy
module.This basically does the same thing as your first code snippet, and is much more readable.
Alternatively (as mentioned by DSM in the comments) and on python >=3, you may also use
words.copy()
which does the same thing.words[:]
copies all the elements inwords
into a new list. So when you iterate overwords[:]
, you're actually iterating over all the elements thatwords
currently has. So when you modifywords
, the effects of those modifications are not visible inwords[:]
(because you called onwords[:]
before starting to modifywords
)In the latter example, you are iterating over
words
, which means that any changes you make towords
is indeed visible to your iterator. As a result, when you insert into index 0 ofwords
, you "bump up" every other element inwords
by one index. So when you move on to the next iteration of your for-loop, you'll get the element at the next index ofwords
, but that's just the element that you just saw (because you inserted an element at the beginning of the list, moving all the other element up by an index).To see this in action, try the following code:
(In addition to @Coldspeed answer)
Look at the below examples:
results:
True
It means names
word
andwords2
refer to the same object.results:
False
In this case, we have created the new object.