В Python есть еще одна встроенная функция, которая часто используется в заголовке for. Это функция enumerate(). Если range() позволяет получить только индексы элементов списка, то enumerate() генерирует пары кортежей, состоящих из индекса элемента и значения элемента.
>>> spisok = [16, 46, 26, 36]
>>> for i in enumerate(spisok):
... print(i)
...
(0, 16)
(1, 46)
(2, 26)
(3, 36)
Эти кортежи можно распаковывать, то есть извлекать индекс и значение, в теле цикла:
>>> for item in enumerate(spisok):
... print(item[0], item[1])
...
0 16
1 46
2 26
3 36
Однако чаще это делают еще в заголовке for, используя две переменные перед in:
>>> for id, val in enumerate(spisok):
... print(id, val)
...
0 16
1 46
2 26
3 36
Если функция enumerate() так хороша, зачем использовать range() в заголовке for? На самом деле незачем, если только вам так не проще. Кроме того, бывают ситуации, когда значения не нужны, нужны только индексы. Однако следует учитывать один нюанс. Функция enumerate() возвращает так называемый объект-итератор. Когда такие объекты сгенерировали значения, то становятся "пустыми". Второй раз по ним пройтись нельзя.
Функция range() возвращает итерируемый объект. Хотя такой объект может быть превращен в итератор, сам по себе таковым не является.
Когда range() и enumerate() используются в заголовке for, то разницы нет, так как range- и enumerate-объекты не присваиваются переменным и после завершения работы цикла теряются. Но если мы присваиваем эти объекты переменным, разница есть:
>>> r_obj = range(len(spisok))
>>> e_obj = enumerate(spisok)
>>> for i in r_obj:
... if i == 1:
... break ...
>>> for i in e_obj:
... if i[0] == 1:
... break ...
>>> for i in r_obj:
... print(i)
...
0
1
2
3
>>> for i in e_obj:
... print(i)
...
(2, 26)
(3, 36)
Сначала мы прерываем извлечение элементов из объектов на элементе с индексом 1. Когда снова прогоняем объекты через цикл for, то в случае r_obj обход начинается сначала, а в случае e_obj продолжается после места останова. Объект e_obj уже не содержит извлеченных ранее элементов.