Индексирование и перебор в Python: индексы, index(), срезы и enumerate

Индексирование и перебор в Python: индексы, index(), срезы и enumerate Полезное

Индекс в Python — это целое число, номер позиции элемента в упорядоченной последовательности: списке, строке, кортеже, range. Отсчет идет с нуля, а отрицательные индексы считают с конца: -1 — последний элемент. По индексу элемент читают (lst[2]), а метод index() решает обратную задачу — по значению находит позицию.

Ниже — как обращаться к элементам по индексу, как искать позицию через index(), как брать части последовательности срезами и как перебирать элементы вместе с их номерами. Все примеры прогнаны на Python 3.14.7 (24.09.2026).

Четыре понятия, которые легко спутать

Понятие Синтаксис Что дает Если не получилось
Индекс (обращение по позиции) lst[i] один элемент IndexError
Метод index() (поиск позиции) lst.index(x) номер первого вхождения x ValueError
Срез lst[a:b:step] новую последовательность-копию части ошибки нет, может быть пусто
enumerate() (перебор с номером) for i, x in enumerate(lst) пары (номер, элемент) —

Главное различие: индекс — это позиция, а index() — это поиск. Выражение fruits[2] работает мгновенно и не зависит от длины списка, а fruits.index("cherry") просматривает элементы по очереди, пока не найдет совпадение.

Минимальный пример целиком

fruits = ["apple", "banana", "cherry", "banana", "kiwi"]

print(fruits[0])            # первый элемент
print(fruits[-1])           # последний
print(fruits.index("banana"))        # позиция первого вхождения
print(fruits.index("banana", 2))     # искать начиная с индекса 2
print(fruits[1:3])          # срез: индексы 1 и 2
for i, name in enumerate(fruits, start=1):
    print(i, name)

Результат:

apple
kiwi
1
3
['banana', 'cherry']
1 apple
2 banana
3 cherry
4 banana
5 kiwi

Разбор по строкам: fruits[0] берет элемент на позиции 0, fruits[-1] — последний. Метод index("banana") возвращает 1, потому что это первое вхождение, хотя «banana» встречается дважды. Со вторым аргументом поиск начинается с позиции 2, и находится второе вхождение — 3. Срез [1:3] включает индекс 1 и не включает 3. Наконец, enumerate(..., start=1) нумерует элементы с единицы — удобно для вывода человеку, но сами индексы списка от этого не меняются.

Положительные и отрицательные индексы

Для строки "Python" индексы выглядят так:

 P   y   t   h   o   n
 0   1   2   3   4   5
-6  -5  -4  -3  -2  -1

Отрицательный индекс — это сокращение для len(s) + i: s[-2] то же, что s[4]. Допустимые индексы для последовательности длины n — от -n до n - 1. Все, что за этими границами, дает ошибку.

nums = [10, 20, 30, 40, 50]
print(nums[5])
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IndexError: list index out of range

В списке 5 элементов, значит последний индекс 4, а не 5. Исправление — брать nums[-1] или nums[len(nums) - 1]. Для строки текст ошибки другой (string index out of range), для индекса-дроби — TypeError: list indices must be integers or slices, not float.

Строку по индексу можно прочитать, но нельзя изменить — строки неизменяемы:

s = "Python"
try:
    s[0] = "J"
except TypeError as e:
    print("TypeError:", e)
s = "J" + s[1:]   # исправление: собрать новую строку
print(s)
TypeError: 'str' object does not support item assignment
Jython

Граница применимости: индексы по позиции есть у последовательностей (list, str, tuple, range, bytes). У словаря в квадратных скобках пишут ключ, а не позицию, а у множества (set) порядка и индексов нет вовсе.

Метод index(): найти позицию по значению

Сигнатура: seq.index(value, start, end), где start и end необязательны и ограничивают участок поиска так же, как срез. Метод есть у списков, кортежей и строк; для строки он ищет подстроку. Возвращается позиция первого вхождения.

Если значения нет, метод не возвращает -1, а выбрасывает исключение:

fruits = ["apple", "banana"]
print(fruits.index("mango"))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: list.index(x): x not in list

Исправление зависит от задачи. Если отсутствие элемента — обычная ситуация, удобна функция с явным значением по умолчанию:

def find_index(seq, value, default=-1):
    try:
        return seq.index(value)
    except ValueError:
        return default

fruits = ["apple", "banana", "cherry"]
print(find_index(fruits, "cherry"))   # 2
print(find_index(fruits, "mango"))    # -1

s = "python"
print(s.find("th"), s.find("zz"))     # 2 -1

Вариант с проверкой if name in fruits: перед index() тоже рабочий, но проходит список дважды. У строк есть готовый find(), который возвращает -1 без исключения; у списков метода find() нет. Осторожно с -1 как признаком «не найдено»: это валидный индекс последнего элемента, поэтому результат find() нельзя сразу подставлять в s[...] без проверки.

Метод index() находит только первое вхождение. Все позиции собирают через enumerate:

nums = [3, 8, 5, 8, 1]
positions = [i for i, x in enumerate(nums) if x == 8]
print(positions)   # [1, 3]

Поиск через index() линейный: в худшем случае просматриваются все n элементов. Если по значению ищут часто и в большом списке, обычно заводят словарь «значение -> позиция».

Срезы: [start:stop:step]

Срез берет часть последовательности. start входит в результат, stop — нет, step задает шаг. Любой параметр можно пропустить: тогда берется начало, конец или шаг 1.

Выражение s = "Python" nums = [0..9]
[1:4] yth [1, 2, 3]
[:2] Py [0, 1]
[2:] thon [2, 3, ..., 9]
[-3:] hon [7, 8, 9]
[::2] Pto [0, 2, 4, 6, 8] — четные позиции
[1::2] yhn [1, 3, 5, 7, 9] — нечетные позиции
[::-1] nohtyP список в обратном порядке

Три отличия среза от обращения по индексу. Первое: выход за границы не вызывает ошибку — nums[7:100] вернет [7, 8, 9], а nums[3:1] вернет пустой список. Второе: срез создает новый объект, исходный не меняется. Третье: копия поверхностная — вложенные объекты общие.

a = [1, 2, 3]
b = a[:]
b[0] = 99
print(a, b)        # [1, 2, 3] [99, 2, 3]

row = [1, 2]
m = [row, [3, 4]]
c = m[:]
c[0][0] = 99
print(m[0])        # [99, 2] - вложенный список общий

Для вложенных структур нужна copy.deepcopy(). У списков срезу можно присвоить значение: a[1:3] = ["x"] заменит два элемента одним, а del a[::2] удалит каждый второй.

Перебор: элементы, индексы или и то и другое

Выбор способа зависит от того, что нужно внутри цикла:

Задача Что взять
Нужны только значения for x in lst
Нужны номер и значение for i, x in enumerate(lst)
Нужно менять элементы по месту for i in range(len(lst)): lst[i] = ...
Идти параллельно по двум спискам for a, b in zip(xs, ys)
Условие остановки не связано с длиной while с явным счетчиком
letters = ["a", "b", "c"]
for i, ch in enumerate(letters):
    print(i, ch)

names = ["Ann", "Bob", "Kate"]
scores = [90, 75, 88]
for name, score in zip(names, scores):
    print(name, score)
0 a
1 b
2 c
Ann 90
Bob 75
Kate 88

Запись for i in range(len(letters)) дает тот же результат, что enumerate, но длиннее и легче ошибиться в индексе. zip останавливается на самом коротком списке, лишние элементы длинного молча отбрасываются. while требует ручного счетчика: забытый i += 1 превращает его в бесконечный цикл.

Типичная ошибка: удаление во время перебора

nums = [1, 2, 2, 3, 4]
for i in range(len(nums)):
    if nums[i] % 2 == 0:
        del nums[i]
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
IndexError: list index out of range

range(len(nums)) вычислен один раз для 5 элементов, а список укорачивается. Вариант for x in nums: с if x % 2 == 0: nums.remove(x) внутри не падает, но молча пропускает элементы: на том же входе останется [1, 2, 3] вместо [1, 3]. Исправление — собрать новый список:

nums = [1, 2, 2, 3, 4]
nums = [x for x in nums if x % 2 != 0]
print(nums)   # [1, 3]

Если не получилось: симптом -> причина

Симптом Частая причина Что сделать
IndexError: list index out of range индекс равен длине или больше последний элемент — len - 1 или -1
ValueError: ... not in list index() не нашел значение try/except или проверка in
TypeError: list indices must be integers индекс — строка или дробь int(...), для деления //
Срез вернул пустой список start правее stop при шаге 1 поменять границы или задать step=-1
Цикл пропускает элементы список меняется во время перебора новый список через генератор списка

Выводы

  • Индекс — позиция элемента с отсчетом от нуля; отрицательный индекс считает с конца, допустимый диапазон от -len до len - 1.
  • index() ищет позицию по значению, возвращает первое вхождение и выбрасывает ValueError, если значения нет; find() с -1 есть только у строк.
  • Срез [start:stop:step] не включает stop, не падает при выходе за границы и возвращает поверхностную копию.
  • Для перебора с номером берут enumerate, для параллельного прохода — zip; изменять список внутри цикла по нему же нельзя.

Где применяется / связь с практикой

Индексы и срезы встречаются почти в любом коде на Python: разбор строк и файлов, обработка ответов API, подготовка данных для анализа, алгоритмические задачи на собеседованиях. На них же строится работа с массивами NumPy и таблицами pandas, где правила срезов похожие, но есть свои отличия.

Освойте тему на практике

Если хочется разобрать эти основы последовательно, с практикой и проверкой кода, подойдет курс «Python-разработчик. Базовый уровень». Бесплатно посмотреть формат занятий можно на открытых уроках Otus.

FAQ

Почему индексы в Python начинаются с нуля?
Индекс удобно понимать как смещение от начала последовательности: у первого элемента смещение 0. Так же устроены C, Java и большинство популярных языков, а срезы вида s[:n] при этом дают ровно n элементов.

Как получить индекс максимального элемента списка?
Самый читаемый способ — nums.index(max(nums)): он вернет позицию первого максимума, но пройдет список дважды. За один проход — max(range(len(nums)), key=nums.__getitem__).

Можно ли обращаться к словарю по индексу?
Нет: d[0] ищет ключ 0. Словари сохраняют порядок вставки, поэтому при необходимости можно сделать list(d)[0], но это создает список всех ключей.

OTUS Журнал