Привет, Хабр! ??
Меня зовут Олег Булыгин, я 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
Странным для не новичка является путать номер элемента с его индексом (смещением элемента относительно начала массива).