Let's have a look at iterator and iterables:
An iterable is an object that has an __iter__ method which returns an
iterator, or which defines a __getitem__ method that can take
sequential indexes starting from zero (and raises an IndexError when
the indexes are no longer valid). So an iterable is an object that you
can get an iterator from.
An iterator is an object with a next (Python 2) or __next__ (Python 3) method.
iter(iterable) returns iterator object, and list_obj[:] returns a new list object, exact copy of list_object.
In your first case:
for w in words[:]
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 iterator
loop calls next method on iterator object to get next item from iterator. This step is repeated until there are no more elements left
loop terminates when a StopIteration exception is raised.
In your second case:
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
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:
>>> 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'
Every time you update your original list before StopIteration you will get the updated iterator and next returns accordingly. That's why your loop runs infinitely.
For more on iteration and the iteration protocol you can look here.