Основы индексации и срезов в 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 от многих других языков. Если индекс позволяет нам извлекать один элемент, то срезы позволяют нам извлекать (или присваивать) сразу несколько элементов. Как и в случае с индексами, выражение помещается после имени объекта в квадратные скобки и имеет следующий базовый синтаксис:
- Значение
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])
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻
Источник: CodeSolid