Срезы
Contents
Срезы#
Срезы встроенных коллекций python
#
Простые срезы#
Списки, строки, кортежи и другие последовательности python
кроме обычной индексации поддерживают индексацию срезами (slices). Рассмотрим на примере строк.
import string
s = string.ascii_lowercase
print(s)
abcdefghijklmnopqrstuvwxyz
Итак, у нас есть строка a
, состоящая из строчных символов английского алфавита.
Если мы хотим извлечь подстроку, начиная с позиции start
включая и заканчивая позицией stop
не включая, то применяется синтаксис
sequence[start:stop]
Например, если нас часть строки с 1-го по 5-ый символ (нумерация с нуля), мы запишем
print(s[1:5])
bcde
Если нас интересует подстрока с самого начала строки до какого-то символа с индексом stop
(не включая), то можно опускать первый параметр среза, т.е. допускается синтаксис
sequence[:stop]
Следующие два среза синонимичны и оба возвращают первые три символа.
print(s[0:3])
print(s[:3])
abc
abc
То же самое справедливо и про второй параметр: если нас интересует подстрока до самого конца исходной строки, начиная с символа с индексом start
(включая), то можно опустить второй параметр, т.е. допускается синтаксис
sequence[start]
Следующие два среза синонимичны и оба возвращают символы с 23-го по конце.
print(s[23:len(s)])
print(s[23:])
xyz
xyz
Допускается даже опустить оба параметра. Тогда возвращается копия строки.
print(s[:])
abcdefghijklmnopqrstuvwxyz
Если один или оба параметра выходят за пределы последовательности, то это не является формальной ошибкой. Результатом будет та часть строки, что пересекается с выбранным срезом, которая может оказаться пустой, если индексы среза не пересекаются с допустимыми индексами строки.
# символы '|' по бокам, чтобы наглядно показать края строки
print(f"|{s[-100:100]}|")
print(f"|{s[90:100]}|")
print(f"|{s[5:2]}|")
|abcdefghijklmnopqrstuvwxyz|
||
||
Срезы с шагом#
Для разнообразия теперь будем брать срезы от списков.
L = list(range(10))
print(L)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Итак, у нас есть список L
чисел от 0 до 9.
Срезы с шагом позволяют извлекать элементы не подряд, а, например, с прореживанием. Для этого предыдущий синтаксис расширяется третьим параметром, который как раз и отвечает за шаг.
sequence[start:stop:step]
Например, чтобы вырезать каждый второй элемент списка L
, начиная с 1-го и заканчивая 8-м (не включая), необходимо записать.
print(L[1:8:2])
[1, 3, 5, 7]
Как и прежде, первые два параметра можно опускать, если start
и stop
совпадают с началом и концом списка соответственно.
Ниже берется каждый второй элемент всего списка.
print(L[::2])
[0, 2, 4, 6, 8]
Шаг может быть отрицательным, но тогда start
должен быть больше step
, чтобы вернулась не пустая подпоследовательность. При этом, как и до этого, start
попадает в срез, а stop
нет.
print(L[8:1:-1])
[8, 7, 6, 5, 4, 3, 2]
Получить последовательность в обратном порядке (обратить последовательность) можно синтаксисом
sequence[::-1]
print(s[::-1])
print(L[::-1])
zyxwvutsrqponmlkjihgfedcba
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Присваивание по срезам#
Если последовательность изменяемая (список, например), то допускается присваивание по срезу.
Если шаг не указан, то можно заменить элементы среза в списке элементами коллекции справа от оператора “=
” вне зависимости от того, совпадает ли количество элементов в коллекции справа от оператора “=
” с размером среза слева от оператора “=
”.
print(L)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Например, код в ячейке ниже заменяет первые два элемента списка L
на новые два элемента (0 заменяется на 3.14, 1 заменяется на 42).
L[:2] = [3.14, 42]
print(L)
[3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]
Код в ячейке ниже заменяет первые два элемента (3.14 и 42 после предыдущей замены) на сразу три новых элемента, тем самым расширяя список.
L[:2] = [0, 3.14, 42]
print(L)
[0, 3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]
Следующим синтаксисом можно удалить первых два элемента.
L[:2] = []
print(L)
[42, 2, 3, 4, 5, 6, 7, 8, 9]
Хотя для этого предпочтительнее воспользоваться синтаксисом
del L[:2]
Если шаг указан, то количество элементов в коллекции справа от “=
: должно совпадать с количеством элементов в срезе
L = list(range(10))
# Oк
print(L[::2])
L[::2] = L[1::2]
# Ошибка
print(L)
L[::2] = []
[0, 2, 4, 6, 8]
[1, 1, 3, 3, 5, 5, 7, 7, 9, 9]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [16], in <cell line: 9>()
7 # Ошибка
8 print(L)
----> 9 L[::2] = []
ValueError: attempt to assign sequence of size 0 to extended slice of size 5
Срезы массивов NumPy
#
Срезы массивов NumPy
введут себя очень похоже с двумя основными отличиями (документация NumPy про срезы):
при присваивании по срезу размер массива не может измениться вне зависимости от того, указан шаг или нет;
допускается делать многомерные срезы.
Срезы одномерных массивов#
Если массив одномерный, то роль играет только первое отличие.
import numpy as np
array = np.arange(10)
print(array)
[0 1 2 3 4 5 6 7 8 9]
print(array[::2])
[0 2 4 6 8]
array[::2] = [9, 7, 5, 3, 1]
print(array)
[9 1 7 3 5 5 3 7 1 9]
Многомерные срезы#
Но если массив многомерный, то можно делать срезы сразу вдоль нескольких осей. Рассмотрим на примере двухмерных массивов.
matrix = np.arange(1, 26).reshape(5, 5)
print(matrix)
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]
[21 22 23 24 25]]
Обратим внимание, что выражением matrix[i, j]
мы получаем элемент массива matrix
на пересечении i
-й строки и j
-й строки.
print(matrix[2, 2])
13
Такая же логика обобщается и на срезы. Например, получить j
-й столбец двухмерного массива matrix
можно выражением
matrix[:, j]
То есть указать вдоль первой оси (axis=0
) указывается полный срез (все строки), а вдоль второй оси (axis=1
) указывается число j
(только j
-й столбец). Получим таким образом нулевой столбец.
print(matrix[:, 0])
[ 1 6 11 16 21]
Итого, процесс получения двухмерного среза можно мысленно представить в следующем виде:
срез, указанный до запятой, вырезает строки из массива;
срез, указанный после запятой, вырезает столбцы из массива;
результирующий срез представляет собой все элементы, что располагаются на пересечении этих строк и столбцов.
Одну i
-ю строку можно получить как выражением matrix[i, :]
, так и выражением matrix[i]
. Рассмотрим более содержательные примеры.
Так, например, чтобы получить элементы на пересечении первых двух строк и столбцов с первого (включая) по четвертый (не включая), необходимо применить следующий срез.
matrix[:2, 1:4]
array([[2, 3, 4],
[7, 8, 9]])
Если требуется получить каждый второй столбец, то вдоль первой оси (axis=0
) указывается полный срез (все строки), а вдоль второй оси (axis=1
) указывается полный срез с шагом 2 (каждый второй столбец).
matrix[:, ::2]
array([[ 1, 3, 5],
[ 6, 8, 10],
[11, 13, 15],
[16, 18, 20],
[21, 23, 25]])
В качестве последнего примера рассмотрим получение срезом элементов из каждой второй строки и каждого второго столбца.
matrix[::2, ::2]
array([[ 1, 3, 5],
[11, 13, 15],
[21, 23, 25]])
Присваивание по срезу#
Срезы ссылаются на данные исходного массива. По ним можно производить присваивание. Например, можно целиком заменить столбец матрицы.
matrix[:, 0] = np.arange(-5, 0)
print(matrix)
[[-5 2 3 4 5]
[-4 7 8 9 10]
[-3 12 13 14 15]
[-2 17 18 19 20]
[-1 22 23 24 25]]
В примере выше формы среза слева от “=
” и массива справа от “=
” совпадают. Это необязательное условие: работают правила броадкастинга, т.е. массив справа от оператора “=
” может быть расширен до размера среза слева от оператора “=
”, если их формы совместимы и срез сможет поместить в себе массив по всем измерениям. Например, прибавим к всем элемента первой строки 100, а каждый второй элемент последней строки сделаем равными 42.
matrix[0, :] += 100
matrix[-1, ::2] = 42
print(matrix)
[[ 95 102 103 104 105]
[ -4 7 8 9 10]
[ -3 12 13 14 15]
[ -2 17 18 19 20]
[ 42 22 42 24 42]]