$ sudo teach IT
Модуль 5 · Коллекции · Урок 5.1

Индексы и срезы

Учимся доставать из строки или списка ровно то, что нужно — один символ, кусок с конца или каждый второй элемент. Один и тот же синтаксис работает для всех коллекций сразу.

Теория + практика~30 минутНовичок

Когда у вас есть строка или набор чисел, рано или поздно понадобится не вся эта штука целиком, а только её часть: первая буква слова, последние три цифры номера телефона, каждый второй элемент списка. Перебирать всё руками неудобно и медленно. В Python для этого есть готовый инструмент — обращение по номеру позиции. Он работает через квадратные скобки после названия строки или списка, и один и тот же приём годится и для одного символа, и для целого куска сразу.

Этот урок открывает модуль про коллекции — наборы данных, где элементов много: строка (набор символов), список, кортеж. Прежде чем разбирать, что можно делать с коллекцией целиком, важно научиться доставать из неё нужный кусок. Это база, без которой не обойдётся почти ни одна следующая тема модуля.

Обращаемся к элементу по номеру

Представьте очередь людей, где каждый стоит на своём месте. Если вы хотите позвать конкретного человека, вы называете не имя, а его место в очереди: «третий, подойдите». Строка в Python устроена так же: каждый символ стоит на своём месте, и к нему можно обратиться по номеру этого места.

Официально это место называется индексом (index). Индекс — это номер позиции элемента внутри строки или списка, начиная с самого первого. Чтобы получить элемент по индексу, после названия переменной пишут квадратные скобки с числом внутри: word[0].

Главная ловушка новичка: нумерация в Python начинается не с 1, а с 0. Первый символ — это индекс 0, второй — индекс 1, и так далее. Считайте это правилом языка, как правостороннее движение — можно спорить, удобно ли оно, но соблюдать нужно всегда.

Разберём на примере строки "Python", в ней 6 символов:

word = "Python"

print(word[0])   # P
print(word[1])   # y
print(word[5])   # n

Разберём по строкам, что здесь происходит:

  • word = "Python" — кладём строку из 6 символов в переменную word.
  • word[0] — квадратные скобки после переменной означают «возьми элемент по этому индексу». Индекс 0 — это первый символ, буква P.
  • word[1] — индекс 1 — это второй символ, буква y. Символ под индексом 1 идёт сразу после символа под индексом 0.
  • word[5] — в слове «Python» 6 букв, поэтому индексы идут от 0 до 5. Индекс 5 — это последний символ, буква n.
Правило: если в строке n символов, то последний доступный индекс — это n - 1, а не n. У строки из 6 символов индексы идут от 0 до 5.

Считаем с конца: отрицательные индексы

Часто нужен не первый элемент, а последний — например, последняя цифра номера или последняя буква слова. Считать длину строки и вычитать единицу, чтобы найти нужный индекс, неудобно. Python решает это проще: разрешает считать не только слева направо, но и справа налево, если поставить перед числом минус.

Такой индекс называется отрицательным индексом. -1 — это последний элемент, -2 — предпоследний, и так далее в сторону начала строки.

word = "Python"

print(word[-1])   # n
print(word[-2])   # o
  • word[-1] — минус означает «считать с конца», число 1 после него — «первый с конца». Это буква n, последняя в слове.
  • word[-2] — второй с конца символ, буква o.
Почему -1, а не 0? Индекс 0 уже занят — это первый элемент слева. Если бы последний элемент тоже назывался 0, было бы непонятно, что означает 0: начало или конец. Поэтому у отрицательного отсчёта своя точка старта, и это -1.

Вырезаем кусок: срез

Индекс достаёт один-единственный символ. А что, если нужно не одно яблоко из вазы, а сразу горсть — например, первые три буквы слова или номер без кода страны? Для этого в квадратных скобках можно указать не одно число, а диапазон через двоеточие.

Такая конструкция называется срезом (slice). Общая запись выглядит так:

коллекция[start:stop]

Здесь start — индекс, с которого начинаем брать элементы (он войдёт в результат), а stop — индекс, на котором останавливаемся, причём сам этот индекс в результат не войдёт.

Самая частая ошибка со срезами: думать, что stop входит в результат. Срез word[1:4] берёт индексы 1, 2, 3 — но не 4. Это работает как открытая справа граница: «от 1 и до 4, не включая саму четвёрку».
word = "Python"

print(word[1:4])   # yth
print(word[0:3])   # Pyt
  • word[1:4] — берём символы с индекса 1 по индекс 3 включительно (индекс 4 не входит): это y, t, h, то есть "yth".
  • word[0:3] — берём индексы 0, 1, 2 — первые три буквы слова, "Pyt".

Оба числа в срезе можно пропустить. Если пропустить start, Python возьмёт с самого начала. Если пропустить stop — возьмёт до самого конца:

word = "Python"

print(word[:3])    # Pyt   -- то же самое, что word[0:3]
print(word[2:])    # thon  -- с индекса 2 и до конца
print(word[:])     # Python -- вообще всё, полная копия строки
  • word[:3] — перед двоеточием ничего нет, значит начинаем с индекса 0. Результат тот же, что и у word[0:3].
  • word[2:] — после двоеточия ничего нет, значит идём до самого конца строки. Получаем "thon".
  • word[:] — пропущены оба числа, значит берём строку целиком, от начала и до конца.

Третий параметр: шаг среза

Иногда нужны не все элементы подряд, а, например, каждый второй — через один. Представьте, что вы идёте по лестнице и перешагиваете через ступеньку: считать всё равно нужно с начала, но останавливаетесь вы не на каждой ступени. У среза для этого есть третий параметр.

Он называется шагом (step) и указывается вторым двоеточием: коллекция[start:stop:step]. Шаг определяет, через сколько элементов делать следующую остановку. По умолчанию шаг равен 1 — берём каждый элемент подряд.

digits = "0123456789"

print(digits[::2])     # 02468
print(digits[1:8:2])   # 1357
  • digits[::2] — start и stop пустые (берём от начала и до конца), шаг 2 — каждый второй символ, начиная с индекса 0: символы 0, 2, 4, 6, 8.
  • digits[1:8:2] — начинаем с индекса 1, останавливаемся перед индексом 8, шаг 2: символы 1, 3, 5, 7.

Если шаг сделать отрицательным, Python пойдёт не вперёд, а назад — от конца строки к началу. Это самый быстрый способ развернуть строку задом наперёд:

word = "Python"

print(word[::-1])   # nohtyP
  • word[::-1] — start и stop снова пустые, что при шаге -1 означает «вся строка», только двигаемся мы теперь от последнего символа к первому. Получаем строку, прочитанную задом наперёд.
Приём, который стоит запомнить: [::-1] — стандартный способ развернуть строку или список. Он же используется, чтобы проверить, является ли строка палиндромом: word == word[::-1] сравнивает строку с её же перевёрнутой версией.

Тот же приём работает и со списками

До сих пор все примеры были со строками, но индексы и срезы устроены совершенно одинаково для любой упорядоченной коллекции элементов. Ближайший пример — список (list): это коллекция, где вместо символов лежат любые значения — числа, строки, что угодно, и порядок между ними сохраняется. Список записывается в квадратных скобках, а значения внутри перечисляются через запятую: [10, 20, 30]. Со списками подробно разберёмся в следующих уроках, а пока используем их только как ещё один пример коллекции с номерами позиций.

scores = [55, 70, 82, 91, 68, 77]

print(scores[0])     # 55
print(scores[-1])    # 77
print(scores[1:4])   # [70, 82, 91]
print(scores[::-1])  # [77, 68, 91, 82, 70, 55]
  • scores[0] — первый элемент списка, число 55. Индексация с нуля работает так же, как у строк.
  • scores[-1] — последний элемент, 77. Отрицательные индексы тоже работают одинаково.
  • scores[1:4] — срез: элементы с индекса 1 по 3 включительно, индекс 4 не входит. Получаем новый список [70, 82, 91].
  • scores[::-1] — список в обратном порядке, всё тем же приёмом, что и для строк.
Единственное отличие: тип результата совпадает с типом исходной коллекции. Срез строки — всегда строка. Срез списка — всегда список. Само правило работы среза (start, stop, step, отрицательные индексы) от этого не меняется.

Полезный трюк: копия списка через [:]

Со строками этот раздел можно пропустить — строку невозможно изменить, поэтому копировать её незачем. А вот со списком есть тонкость: если просто присвоить список другой переменной, вы получите не копию, а второе имя для того же самого списка. Изменения через одно имя будут видны и через другое — как две ручки от одной и той же двери.

Срез [:], который берёт коллекцию целиком, решает эту проблему: он не просто ссылается на тот же список, а создаёт новый, независимый, с такими же элементами.

original = [1, 2, 3]
alias = original
copy = original[:]

copy.append(99)

print(original)   # [1, 2, 3]
print(copy)        # [1, 2, 3, 99]
  • alias = original — это не копия: alias и original указывают на один и тот же список в памяти.
  • copy = original[:] — срез без границ создаёт новый список с теми же элементами, но отдельный от исходного.
  • copy.append(99) — метод .append(значение) добавляет элемент в конец списка, у которого он вызван. Здесь мы вызываем его у copy, поэтому меняется только copy.
  • Из-за этого print(original) покажет прежние три числа, а print(copy) — уже четыре: добавление затронуло только независимую копию.

Частые ошибки

IndexError за пределами коллекции. Если запросить одиночный индекс, которого не существует, Python остановит программу с ошибкой:

fruits = ["яблоко", "банан", "вишня"]

print(fruits[3])
# IndexError: list index out of range

В списке fruits три элемента, значит доступны только индексы 0, 1, 2. Индекса 3 не существует, и Python сообщает об этом ошибкой вместо того, чтобы вернуть пустое значение. Это стоит воспринимать как подсказку: где-то в расчёте индекса ошибка, обычно на единицу.

А вот срезы не кидают IndexError. Если границы среза выходят за пределы коллекции, Python просто берёт то, что есть, без ошибки:
fruits = ["яблоко", "банан", "вишня"]

print(fruits[1:100])   # ['банан', 'вишня']
print(fruits[10:20])   # []

fruits[1:100] просит элементы с индекса 1 до 99, но список заканчивается раньше — Python тихо останавливается на последнем реальном элементе. fruits[10:20] начинается уже за пределами списка, поэтому результат — пустой список [], а не ошибка. Разница с одиночным индексом принципиальная: индекс требует, чтобы позиция существовала, а срез — нет.

Смотреть урок на

Что важно запомнить

1.Индекс — номер позиции элемента, отсчёт начинается с 0. У коллекции из n элементов последний индекс — n - 1.
2.Отрицательный индекс считает с конца: -1 — последний элемент, -2 — предпоследний.
3.Срез [start:stop:step] вырезает кусок коллекции. stop в результат не входит: [:3] берёт индексы 0, 1, 2.
4.[::-1] разворачивает строку или список задом наперёд. [:] делает независимую копию.
5.Синтаксис одинаковый для строк, списков и кортежей. Тип результата среза всегда совпадает с типом исходной коллекции.
6.Одиночный индекс за пределами коллекции вызывает IndexError. Срез за пределами коллекции ошибки не вызывает — просто берёт то, что есть.

Проверьте себя

5 вопросов

Срезы строки

Premium

Срезы списка

Premium

Разворот строки

Дана строка word. С помощью среза с шагом -1 получите строку, записанную задом наперёд, и сохраните её в переменную reversed_word.

Например, для word = "привет" должно получиться "тевирп".