Show enumerate and zip in use, including what zip does when the two sequences have different lengths.
enumerate wraps an iterable and gives you index-and-value pairs, while zip places the items of several iterables next to each other.
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
print(index, fruit)
# The start argument changes where the numbering begins.
print(list(enumerate(fruits, start=1)))
numbers = [10, 20, 30]
letters = ['x', 'y']
print(list(zip(numbers, letters)))
The first loop prints 0 apple, 1 banana, and 2 cherry. The enumerate call with start=1 gives the pairs (1, 'apple'), (2, 'banana'), and (3, 'cherry'). The zip of a three-item list with a two-item list gives only two pairs, which are (10, 'x') and (20, 'y'). The value 30 has no partner, so it is dropped without any warning.
That truncation is the single most important fact about zip. It stops as soon as the shortest iterable is finished, and it does not tell you that it has done so. It is exactly what you want when you are pairing two files that are guaranteed to line up, and it is a silent data-loss bug when they are not.
Two details about enumerate are worth knowing. The start argument takes any number, so enumerate(items, start=1) gives one-based numbering, which is often what you want for output that people read. And enumerate does not need the iterable to support indexing, so it works with generators, which is a real advantage over the older habit of writing range(len(items)).
Finally, zip is lazy, which means it produces pairs as it goes, so you should wrap it in list() when you want a concrete list that you can index into or reuse.