Срезы

Срезы встроенных коллекций python

Простые срезы

Списки, строки, кортежи и другие последовательности python кроме обычной индексации поддерживают индексацию срезами (slices). Рассмотрим на примере строк.

import string

a = string.ascii_lowercase
a
'abcdefghijklmnopqrstuvwxyz'

Итак, у нас есть строка a, состоящая из строчных символов английского алфавита.

Если мы хотим извлечь подстроку, начиная с позиции start включая и заканчивая позицией stop не включая, то применяется синтаксис

sequence[start:stop]

Например, если нас часть строки с 1-го по 5-ый символ (нумерация с нуля), мы запишем

print(a[1:5])
bcde

Если нас интересует подстрока с самого начала до какого-то индекс stop, то допускается опускать первый параметр среза

print(a[0:3])
print(a[:3])
abc
abc

Аналогично, и со вторым параметром: если на интересует подстрока до самого конца исходный строки, то можно опустить второй параметр.

print(a[23:len(a)])
print(a[23:])
xyz
xyz

Если опустить оба параметра, то возвращается полная копия строки.

print(a[:])
abcdefghijklmnopqrstuvwxyz

Если в срезе выйти за пределы последовательности, то никакой ошибки не будет. Результатом будет та часть строки, что пересекается с выбранным срезом, которая может оказаться пустой, если индексы среза не пересекаются с допустимыми индексами строки.

# символы '|' по бокам, чтобы наглядно показать, что напечаталась пустая строка
print(f"|{a[-100:100]}|")
print(f"|{a[90:100]}|")
print(f"|{a[5:2]}|")
|abcdefghijklmnopqrstuvwxyz|
||
||

Срезы с шагом

Для разнообразия теперь будем брать срезы от списков.

b = list(range(10))
print(b)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Итак, у нас есть список b чисел от 0 до 9.

Срезы с шагом позволяют извлекать элементы не подряд, а, например, с прореживанием. Для этого предыдущий синтаксис расширяется третьим параметром, который как раз и отвечает за шаг.

sequence[start:stop:step]

Например, чтобы вырезать каждый второй элемент списка b, начиная с 1-го и заканчивая 8-м (не включая), необходимо записать.

print(b[1:8:2])
[1, 3, 5, 7]

Как и прежде, первые два параметра можно опускать, если start и stop совпадают с началом и концом списка соответственно.

Ниже берется каждый второй элемент всего списка.

print(b[::2])
[0, 2, 4, 6, 8]

Шаг может быть отрицательным, но тогда start должен быть больше step, чтобы вернулась не пустая подпоследовательность. При этом, как и до этого, start попадает в срез, а stop нет.

print(b[8:1:-1])
[8, 7, 6, 5, 4, 3, 2]

Получить последовательность в обратном порядке (обратить последовательность) можно синтаксисом

sequence[::-1]
print(a[::-1])
print(b[::-1])
zyxwvutsrqponmlkjihgfedcba
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Присваивание по срезам

Если последовательность изменяемая (список, например), то допускается присваивание по срезу.

Если шаг не указан, то можно заменить элементы среза в списке элементами коллекции справа от оператора = вне зависимости от того, совпадает ли их количество или нет

print(b)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Например, код в ячейке ниже заменяет первые два элемента списка b на новые два элемента (0 заменяется на 3.14, 1 заменяется на 42).

b[:2] = [3.14, 42]
print(b)
[3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]

Код в ячейке ниже заменяет первые два элемента (3.14 и 42 после предыдущей замены) на сразу три новых элемента, тем самым расширяя список.

b[:2] = [0, 3.14, 42]
print(b)
[0, 3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]

Следующим синтаксисом можно удалить первых два элемента.

b[:2] = []
print(b)
[42, 2, 3, 4, 5, 6, 7, 8, 9]

Хотя для этого предпочтительнее воспользоваться синтаксисом

del b[:2]

Если шаг указан, то количество элементов в коллекции справа от = должно совпадать с количеством элементов в срезе

b = list(range(10))

print(b[::2])
b[::2] = b[1::2]
print(b)
b[::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: 6>()
      4 b[::2] = b[1::2]
      5 print(b)
----> 6 b[::2] = []

ValueError: attempt to assign sequence of size 0 to extended slice of size 5

Срезы массивов NumPy

Срезы NumPy введут себя очень похоже с двумя основными отличиями (документация NumPy про срезы):

  • при присваивании по срезу размер массива не может измениться вне зависимости от того, указан шаг или нет;

  • они допускают многомерные срезы;

Если массив одномерный, то роль играет только первое отличие.

import numpy as np
x = np.arange(10)
print(x)
[0 1 2 3 4 5 6 7 8 9]
print(x[::2])
[0 2 4 6 8]
x[::2] = [1, 3, 5, 7, 9]
print(x)
[1 1 3 3 5 5 7 7 9 9]

Но если массив многомерный, то можно делать срезы сразу вдоль нескольких осей.

A = np.arange(100).reshape(10, 10)
print(A)
[[ 0  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 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
A[2:4, 2:4]
array([[22, 23],
       [32, 33]])
A[:, ::2]
array([[ 0,  2,  4,  6,  8],
       [10, 12, 14, 16, 18],
       [20, 22, 24, 26, 28],
       [30, 32, 34, 36, 38],
       [40, 42, 44, 46, 48],
       [50, 52, 54, 56, 58],
       [60, 62, 64, 66, 68],
       [70, 72, 74, 76, 78],
       [80, 82, 84, 86, 88],
       [90, 92, 94, 96, 98]])

Процесс получения двухмерного среза можно мысленно представить в следующем виде:

  • срез, указанный до запятой, вырезает строки из массива;

  • срез, указанный после запятой, вырезает столбцы из массива;

  • результирующий срез представляет из себя все то, что располагается на пересечении этих строк и столбцов.

Это позволяет получить i-ю строку выражением

A[i, :]

а j-й столбец выражением

A[:, j]
print(A[0, :])
[0 1 2 3 4 5 6 7 8 9]
print(A[:, 0])
[ 0 10 20 30 40 50 60 70 80 90]

Присваивание по срезу

Срезы ссылаются на данные исходного массива. По ним можно производить присваивание. Например, можно целиком заменить столбец матрицы.

A[:, 0] = np.arange(-10, 0)
print(A)
[[-10   1   2   3   4   5   6   7   8   9]
 [ -9  11  12  13  14  15  16  17  18  19]
 [ -8  21  22  23  24  25  26  27  28  29]
 [ -7  31  32  33  34  35  36  37  38  39]
 [ -6  41  42  43  44  45  46  47  48  49]
 [ -5  51  52  53  54  55  56  57  58  59]
 [ -4  61  62  63  64  65  66  67  68  69]
 [ -3  71  72  73  74  75  76  77  78  79]
 [ -2  81  82  83  84  85  86  87  88  89]
 [ -1  91  92  93  94  95  96  97  98  99]]

В примере выше формы среза слева от “=” и массива справа от “=” совпадают. Это необязательное условие: работают правила броадкастинга. Например, прибавим к всем элемента первой строки 100, а каждый элемент последней строки сделаем равными 42.

A[0, :] += 100
A[-1, ::2] = 42
print(A)
[[ 90 101 102 103 104 105 106 107 108 109]
 [ -9  11  12  13  14  15  16  17  18  19]
 [ -8  21  22  23  24  25  26  27  28  29]
 [ -7  31  32  33  34  35  36  37  38  39]
 [ -6  41  42  43  44  45  46  47  48  49]
 [ -5  51  52  53  54  55  56  57  58  59]
 [ -4  61  62  63  64  65  66  67  68  69]
 [ -3  71  72  73  74  75  76  77  78  79]
 [ -2  81  82  83  84  85  86  87  88  89]
 [ 42  91  42  93  42  95  42  97  42  99]]

Теперь возьмем все элементы на пересечении 4-6 строк и столбцов (включительно) и обнулим их.

A[3:7, 3:7] = 0
print(A)
[[ 90 101 102 103 104 105 106 107 108 109]
 [ -9  11  12  13  14  15  16  17  18  19]
 [ -8  21  22  23  24  25  26  27  28  29]
 [ -7  31  32   0   0   0   0  37  38  39]
 [ -6  41  42   0   0   0   0  47  48  49]
 [ -5  51  52   0   0   0   0  57  58  59]
 [ -4  61  62   0   0   0   0  67  68  69]
 [ -3  71  72  73  74  75  76  77  78  79]
 [ -2  81  82  83  84  85  86  87  88  89]
 [ 42  91  42  93  42  95  42  97  42  99]]

Чуть более сложный пример.

end = f"\n{'-'*80}\n"
A[:, -3:] = np.array([1, 2, 3])
print(A, end=end)
A[::2, -3:] = np.array([4, 5, 6])
print(A, end=end)
A[-3:, :] = np.array([[7], [8], [9]])
print(A)
[[ 90 101 102 103 104 105 106   1   2   3]
 [ -9  11  12  13  14  15  16   1   2   3]
 [ -8  21  22  23  24  25  26   1   2   3]
 [ -7  31  32   0   0   0   0   1   2   3]
 [ -6  41  42   0   0   0   0   1   2   3]
 [ -5  51  52   0   0   0   0   1   2   3]
 [ -4  61  62   0   0   0   0   1   2   3]
 [ -3  71  72  73  74  75  76   1   2   3]
 [ -2  81  82  83  84  85  86   1   2   3]
 [ 42  91  42  93  42  95  42   1   2   3]]
--------------------------------------------------------------------------------
[[ 90 101 102 103 104 105 106   4   5   6]
 [ -9  11  12  13  14  15  16   1   2   3]
 [ -8  21  22  23  24  25  26   4   5   6]
 [ -7  31  32   0   0   0   0   1   2   3]
 [ -6  41  42   0   0   0   0   4   5   6]
 [ -5  51  52   0   0   0   0   1   2   3]
 [ -4  61  62   0   0   0   0   4   5   6]
 [ -3  71  72  73  74  75  76   1   2   3]
 [ -2  81  82  83  84  85  86   4   5   6]
 [ 42  91  42  93  42  95  42   1   2   3]]
--------------------------------------------------------------------------------
[[ 90 101 102 103 104 105 106   4   5   6]
 [ -9  11  12  13  14  15  16   1   2   3]
 [ -8  21  22  23  24  25  26   4   5   6]
 [ -7  31  32   0   0   0   0   1   2   3]
 [ -6  41  42   0   0   0   0   4   5   6]
 [ -5  51  52   0   0   0   0   1   2   3]
 [ -4  61  62   0   0   0   0   4   5   6]
 [  7   7   7   7   7   7   7   7   7   7]
 [  8   8   8   8   8   8   8   8   8   8]
 [  9   9   9   9   9   9   9   9   9   9]]