Привет, Хабр! ??
Меня зовут Олег Булыгин, я data scientist, аналитик, автор и спикер IT-курсов.
Я готовлю разный полезный контент, туториалы и руководства по Python, которыми бы хотел делиться с вами :)
Этот материал для самых начинающих. Опытные, не серчайте, у новичков всегда есть запрос на разнообразные статьи по самым основам ;)

Все сталкиваются с индексами на самых ранних стадиях освоения языка, как правило, при изучении списков. Вероятно, вы и так знаете, что индексация в Python начинается с нуля. У нас есть список movies, тогда операция movies[0] вернёт первый элемент списка.
Да, для новичков считать от нуля до девяти при работе со списком из десяти элементов поначалу кажется немного странным. Python в этом не уникален — в большинстве языков программирования реализован такой же подход (C, C++, Java, C# и JavaScript).
Стоит обсудить не то, чем индексация в Python похожа на другие языки, а чем от них отличается. Например:
Она почти никогда не используется в циклах. Да, мы можем перебирать индексы элементов объекта в цикле
forвместо перебора собственно элементов, но это не норма.Можно использовать отрицательные индексы, они начинаются с -1. -1 возвращает последний элемент, -2 возвращает предпоследний и так далее.
Для извлечения сразу нескольких элементов можно использовать расширенную форму индексации — срезы. Используя срезы в сочетании с отрицательными индексами можно, например, развернуть последовательность. Также можно указывать шаг среза для составления гибких правил извлечения нужных элементов.
Если вы новичок в Python, и вам пока не знакомы эти концепции, то в этой статье мы как раз рассмотрим несколько практических примеров.
Простая прямая индексация
Давайте начнём с нескольких простых примеров прямой индексации, используя список, кортеж и строку. Как показано ниже, индекс — это число, заключённое в квадратные скобки, которое мы ставим после составного объекта.
numbers = [42, 1941, 1066, 1969]
indexes = "Всё очень просто!"
names = ("Оруэлл", "Хаксли", "Замятин")
print(numbers[0])
# 42
last_index = len(indexes) - 1
print(indexes[last_index])
# !
print(f"Нас ждёт будущее, как в книгах {names[1]}.")
# Нас ждёт будущее, как в книгах Хаксли.
Опять же, во всех случаях индекс первого элемента равен нулю, а последнего — длина объекта минус единица. Использование индекса за пределами этого диапазона приведёт к тому, что Python выдаст ошибку IndexError.

А теперь давайте обсудим нюансы индексации, которые специфичны именно для Python.
Если мы работаем с изменяемыми типами данных (те же списки), то индексы могут быть использованы не только для извлечения значений, но и для присваивания (замены элементов изменяемого объекта).
numbers = [1, 2, 8, 4]
print(numbers)
# [1, 2, 8, 4]
# Изменяем третий элемент списка
numbers[2] = 3
print(numbers)
# [1, 2, 3, 4]
Обратная индексация в Python
Обратная индексация в Python предполагает доступ к элементам при помощи отрицательных чисел. Она начинается с конца объекта и идёт в обратном порядке. То есть, мы можем получить последний элемент при помощи индекса -1. Доступ к предпоследнему элементу можно получить с помощью -2 и так далее.
Использование отрицательных индексов может быть полезно при работе со списками вариативной длины. Так удобно получать доступ к элементам из конца списка, не зная заранее длину списка.
Давайте возьмём последний символ из строки Zen of Python, используя прямую и обратную индексацию:
saying = "Simple is better than complex"
# получаем последний элемент прямой индексацией
print(saying[len(saying) - 1])
# x
# используем обратную
print(saying[-1])
# x
Работа с индексами в цикле for
Как мы упоминали выше, в общем случае индексы не используются в циклах Python хотя в некоторых языках без индексов не реализовать итерацию по элементам составного объекта. Вот, например, как это может выглядеть на C:
#include <stdio.h>
int main(void)
{
const int LEN = 3;
char chars[LEN] = {"A", "B", "C"};
for(int i = 0; i < LEN; i++)
{
printf("Найден символ по индексу %d: %c\n", i, chars[i]);
}
}
/*
Найден символ по индексу 0: A
Найден символ по индексу 1: B
Найден символ по индексу 2: C
*/
В Python, конечно, можно выполнять итерации по списку гораздо проще:
chars = ["A", "B", "C"]
for char_ in chars:
print(char_)
# A
# B
# C
Так всегда и нужно писать за исключением редких ситуаций, когда нам напрямую нужно оперировать с индексами в рамках логики какого-то алгоритма. Тут поможет функция enumerate, которая позволяет получить и индекс, и значение одновременно. Вот как мы можем получить тот же результат, что и в коде C:
chars = ["A", "B", "C"]
for index, char in enumerate(chars):
print(f"Найден символ по индексу {index}: {char}")
# Найден символ по индексу 0: A
# Найден символ по индексу 1: B
# Найден символ по индексу 2: C
Срезы Python: индексы на стероидах
Срезы — главное, что отличает функционал индексов в Python от многих других языков. Если индекс позволяет нам извлекать один элемент, то срезы позволяют нам извлекать (или присваивать) сразу несколько элементов. Как и в случае с индексами, выражение помещается после имени объекта в квадратные скобки и имеет следующий базовый синтаксис:
sequence[start:stop:step]
Значение
start— это целое число, которое является началом (левой границей) среза. Если его не ставить, то по умолчанию равен нулю, то есть началу последовательности.Значение
stop— это целое число, представляющее собой конец среза (его правую границу). Очень важно помнить, что правая граница предполагает нужный вам последний индекс + 1. То есть правая граница сама по себе в результат не входит. Если её не ставить, то по умолчанию используется длина объекта («до самого конца»).Значение
step— целое число, шаг среза, по умолчанию равен 1. Шаг среза последовательно прибавляется к каждому индексу от левой границы до правой, результирующие элементы будут в выборке. Т.е. если шаг равен 1, то берётся каждый элемент, если 2 — через один. А если шаг равен -1, то элементы выбираются справа налево.
Давайте посмотрим на это наглядно, начав со срезов с прямой индексацией:
numbers = [1, 2, 3, 4, 5]
# срезы от нуля до двух, с явным или неявным началом среза
print("Индексы от нуля до двух")
print(numbers[0:3])
print(numbers[:3]) # вариант аналогичный предыдущему
# Индексы от нуля до двух
# [1, 2, 3] [1, 2, 3]
# Индексы от 3 до конца списка
print("\nИндексы от 3 до конца списка")
print(numbers[3:len(numbers)])
print(numbers[3:])
# Индексы от 3 до конца списка
# [4, 5]
# [4, 5]
# Делаем неглубокую копию списка
print("\nКопия списка")
print(numbers[:])
# Копия списка
# [1, 2, 3, 4, 5]
# Получем все элементы через 1
print("\nС шагом 2:")
print(numbers[::2])
# С шагом 2:
# [1, 3, 5]
Срезы могут быть удобными, например, для удаления фиксированного префикса из строк:
# Удаляем "id-" из строк в списке:
order_items = ["id-999", "id-19098", "id-2"]
cleaned = [item[3:] for item in order_items]
print(cleaned)
# ['999', '19098', '2']
В срезах, конечно, можно использовать и обратную индексацию. Если задать шаг -1, то получим элементы в обратном порядке.
numbers = [1, 2, 3, 4, 5]
print(numbers[::-1])
print(numbers[4:2:-1])
# [5, 4, 3, 2, 1]
# [5, 4]
Отрицательный шаг в срезах в реальной практике используется нечасто, но на собеседованиях вы вполне можете натолкнуться на вопрос о том, как развернуть строку. Это можно сделать и при помощи цикла, но не такого ответа в идеале от вас ожидают :)
Срезы для присваивания
Присвоение по срезу заменяет часть составного объекта содержимым другого составного объекта. Количество добавляемых элементов не обязательно должно соответствовать количеству элементов в срезе, т.к. список без проблем увеличиться или уменьшиться, если их будет больше или меньше.
Например:
count_to_ten = [num for num in range(1, 11)] # список с числами от 1 до 10
count_to_ten[3:6] = [20, 30]
count_to_ten
count_to_ten[6:8] = [100, 200, 300, 400]
print(count_to_ten)
# [1, 2, 3, 20, 30, 7, 100, 200, 300, 400, 10]
Напоминаем, что для строк и других неизменяемых типов данных присваивание по индексу/срезу работать не будет.
Задачки по индексации и срезам
Решите несколько небольших задачек самостоятельно для закрепления материара:
1. Что мы получим в результате запуска приведённого ниже кода? Сможете ли вы заменить все строки, кроме импорта, на один print, который выведет аналогичную строку?
from string import ascii_uppercase
subset = ""
for idx, letter in enumerate(ascii_uppercase):
if idx % 4 == 0:
subset = subset + letter
print(subset)
2. Используя ascii_uppercase, выведите алфавит в обратном порядке при помощи среза.
3. Находим иголку в стоге сена. При помощи срезов, метода index и функции len выведите строку "иголка", где бы она ни располагалась в example.
example = "сено сено сено иголка сено сено сено, привет, привет, пока."
4. При помощи среза из приведённого ниже списка выведите такой результат:[9, 6, 3]
count_to_ten = [num for num in range(1,11)]
print(count_to_ten)
5. Из имеющегося списка при помощи индексов выведите на экран только слово "клубнику".
tokens = "Тут хоть где-нибудь можно купить клубнику?".split(" ")
print(tokens)
6. Как думаете, что мы увидим в результате вызова claim.index("Python")?
claim = "В том материалы вы узнали про индексы и срезы в Python".split()
print(claim)
7. Что увидим на экране в качестве вывода?
greeting = "Hello"
print(greeting[4])
print(greeting[5])
?Если тебе интересны и другие полезные материалы по Python и IT, то подписывайся на мой канал в tg: PythonTalk ?
Комментарии (5)

rSedoy
29.04.2024 06:41+1for char_ in chars:
а что стало причиной появление подчеркивания?
Yuri0128
29.04.2024 06:41Ну ХЗ что автор хотел, делая такую запись. Но, в принципе, - вполне себе нормальная практика именовать, заменяя "s" в именах на "_" при обходе списков (ну и др. п.в.). Вполне понятно что и к чему относится. Ну, на мой взгляд.
Вот почему в других местах не так у автора - вот это уже вопрос....

Yuri0128
29.04.2024 06:41без индексов цикл просто не написать. Вот, например, как это может выглядеть на C:
Ну... Во-первых i в данном случае - это счетчик цикла и аж никак не индекс чего-то там.. То, что вы его используете в качестве индекса, в целом его назначение не меняет.
Во-вторых - ну используйте цикл while() - там нету счетчика цикла.
Ну и даже цикл со счетчиком в С-ях будет отрабатываться сильно быстрее кода на Пайтоне...
PS Вы бы еще Пайтон с ассебмлером сравнили - вот там без индексной адресации сильно неудобнее реализация будет. А с индексной - шустро выходит.

SatCat
29.04.2024 06:41Автор не рассказал о работе со срезом в виде конструкции my_list[0:0] = [1,2,3]
rsashka
Странным для не новичка является путать номер элемента с его индексом (смещением элемента относительно начала массива).