range

Например, дана задача: в массиве чисел к каждому элементу прибавить его индекс. Можно эту задачу решить через цикл while, но всё же хотелось бы через for.

Вот пример решения через цикл for:

array = [1, 5, -2, 8, 0, 2]
index = 0
for element in array:
    print(element + index)
    index = index + 1

Нам понадобилась дополнительная переменная для хранения текущего индекса. Но хочется иметь способ сделать наоборот: брать индексы, а по ним иметь доступ к элементам. И желательно без дополнительной переменной.

В Python так можно. А чтобы брать индексы, и в принципе последовательности чисел, есть функция range:

array = [1, 5, -2, 8, 0, 2]
for index in range(len(array)):
    print(index + array[index])

Что же делает функция range? Она, будучи вызвана с одним аргументом, возвращает объект, поддерживающий протокол итерирования, который "пробегает" значения от нуля до этого аргумента (не включая значение самого аргумента), с шагом 1.

Что такое "объект, поддерживающий протокол итерирования"? Это как список, или массив, но только для нужд цикла for. Если попробовать распечатать range, то мы увидим не список значений:

>>> print(range(10))
range(0, 10)

Но в цикле for этот объект ведёт себя также, как и массив с числами от 0 до 10. И объект занимает меньше места в памяти, чем массив.

Если она вызвана с двумя аргументами, то возвращаемый объект "пробегает" значения от первого аргумента до второго.

>>> for i in range(5, 8):
...     print(i)
...
5
6
7

Если же функция range вызвана с тремя аргументами, то третий аргумент - это шаг изменения значения. Он может быть и отрицательным:

>>> for i in range(8, 5, -2):
...     print(i)
...
8
6