iter() does nothing to a list; the list object has an __iter__ method that iter() uses to produce an iterator object. That object has a reference to the original list and an index; every time you ask for the next value in the iterator, the value at the current index is retrieved and returned, and the index is incremented.
You can use the next() function to get the next value from an iterator:
>>> a = ['animal', 'dog', 'car', 'bmw', 'color', 'blue']
>>> a_iter = iter(a)
>>> next(a_iter) # get the next value
'animal'
>>> next(a_iter) # get the next value
'dog'
Notice how calling next() again gives you a new value. You can do so until the iterator is done:
>>> three_more = next(a_iter), next(a_iter), next(a_iter)
>>> next(a_iter) # last one
'blue'
>>> next(a_iter) # nothing left
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
List iterator objects keep hold of the original list object; changing the list object will reflect in the iterator values produced on next():
>>> b = ['foo', 'bar']
>>> b_iter = iter(b)
>>> next(b_iter)
'foo'
>>> b[1] = 'spam'
>>> b
['foo', 'spam']
>>> next(b_iter)
'spam'
zip() asks for the next value in each of its arguments, which are assumed to be iterables; zip() calls iter() on them all. For iterator objects such as a_iter, iter(a_iter) returns the iterator itself (it's already an iterator, after all):
>>> iter(a_iter)
<list_iterator object at 0x10e7b6a20>
>>> iter(a_iter) is a_iter
True
Since a_iter will yield values from the original list, in order, that means that you get paired-up elements in a dictionary, because zip() has two references to the same object; you effectively creating (next(a_iter), next(a_iter)) as the iterator step values for zip(). If you pass in two references to a, on the other hand, zip() will call iter() twice, creating two separate iterator objects, and each have their own index to track.
Let's look at that in detail. Note that zip() also produces an iterator object, so we can verify that calling next() on zip() in turn causes a_iter to step forward twice:
>>> a_iter = iter(a)
>>> a_iter_zip = zip(a_iter, a_iter)
>>> a_iter_zip # a zip object is an iterator too
<zip object at 0x10e7ba8c8>
>>> next(a_iter_zip) # get next value of a_iter, together with the next value of a_iter
('animal', 'dog')
>>> next(a_iter) # the a-list iterator was advanced, so now we get 'car'
'car'
>>> next(a_iter_zip) # now a_iter is at bmw, so we get bmw and color
('bmw', 'color')
Iterators are independent objects, they each have their own index:
>>> a_iter1 = iter(a)
>>> a_iter2 = iter(a) # different iterator from a_iter1
>>> next(a_iter1), next(a_iter1) # what zip() does
('animal', 'dog')
>>> next(a_iter2), next(a_iter2) # iter2 is independent
('animal', 'dog')
So when you use zip(a, a), what really happens is that zip() calls iter(a) two times, creating two new iterators, and both are used to create the output:
>>> a_iter1 = iter(a)
>>> a_iter2 = iter(a)
>>> a_iter_1_and_2_zip = zip(a_iter1, a_iter2)
>>> next(a_iter_1_and_2_zip) # values from a_iter1 and a_iter2
('animal', 'animal')
>>> next(a_iter_1_and_2_zip) # moving in lockstep
('dog', 'dog')
>>> next(a_iter1) # moving one of these two one step along, to 'car'
'car'
>>> next(a_iter_1_and_2_zip) # so a_iter1 is one step ahead!
('bmw', 'car')
>>> next(a_iter1) # another extra step
'color'
>>> next(a_iter_1_and_2_zip) # so a_iter1 is two steps ahead!
('blue', 'bmw')
forcycles for speed, you can always transform an iterator to a list bylist().