Основы индексации и срезов в Python

—

от автора

Привет, Хабр! ??

Меня зовут Олег Булыгин, я 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 ?


ссылка на оригинал статьи https://habr.com/ru/articles/811247/