1.7. The range built-in function is useful for loops

that iterate over a set of integers:

>>> from random import randint
>>>
>>> random_bits = 0
>>> for i in range(32):
>>>     if randint(0, 1):
>>>         random_bits |= 1 << i
>>>
>>> print(bin(random_bits))
0b1110000101010010101011010000101

When you have a data structure to iterate over, like a list of strings, you can loop directly over the sequence:

>>> flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
>>> for flavor in flavor_list:
>>>      print(f'{flavor} is delicious')
vanilla is delicious
chocolate is delicious
pecan is delicious
strawberry is delicious

Often, you’ll want to iterate over a list and also know the index of the current item in the list. For example, say that I want to print the ranking of my favorite ice cream flavors. One way to do it is by using range:

>>> for i in range(len(flavor_list)):
>>>     flavor = flavor_list[i]
>>>     print(f'{i + 1}: {flavor}')
1: vanilla
2: chocolate
3: pecan
4: strawberry

This looks clumsy compared with the other examples of iterating over flavor_list or range. I have to get the length of the list. I have to index into the array. The multiple steps make it harder to read.

Python provides the enumerate built-in function to address this situation. enumerate wraps any iterator with a lazy generator (see Item 30: “Consider Generators Instead of Returning Lists”). enumerate yields pairs of the loop index and the next value from the given iterator. Here, I manually advance the returned iterator with the next built-in function to demonstrate what it does:

>>> it = enumerate(flavor_list)
>>> print(next(it))
>>> print(next(it))
(0, 'vanilla')
(1, 'chocolate')

Each pair yielded by enumerate can be succinctly unpacked in a for statement (see Item 6: “Prefer Multiple Assignment Unpacking Over Indexing” for how that works). The resulting code is much clearer:

>>> for i, flavor in enumerate(flavor_list):
>>>     print(f'{i + 1}: {flavor}')
1: vanilla
2: chocolate
3: pecan
4: strawberry

I can make this even shorter by specifying the number from which enumerate should begin counting (1 in this case) as the second parameter:

>>> for i, flavor in enumerate(flavor_list, 1):
>>>     print(f'{i}: {flavor}')
1: vanilla
2: chocolate
3: pecan
4: strawberry

1.7.1. Things to Remember

✦ enumerate provides concise syntax for looping over an iterator and getting the index of each item from the iterator as you go.

✦ Prefer enumerate instead of looping over a range and indexing into a sequence.

✦ You can supply a second parameter to enumerate to specify the number from which to begin counting (zero is the default).