Настало время перейти к очень интересному инструменту, который Python предоставляет для работы с целыми подмножествами элементов списка: к так называемым срезам (slices).
Синтаксис описания срезов
Срезы встроены в язык и снабжены своим собственным синтаксисом — настолько широко они используются. Срез записывается так же, как записывается обращение к элементу списка по индексу:
some_list[START:STOP:STEP]
Всего у среза три параметра:
START
— индекс первого элемента в выборкеSTOP
— индекс элемента списка, перед которым срез должен закончиться. Сам элемент с индексомSTOP
не будет входить в выборкуSTEP
— шаг прироста выбираемых индексов
Математически говоря, в множество будут входить индексы элементов, которые будут выбраны:
(START, START + STEP, START + 2 * STEP, .., STOP) # STOP не входит в срез
Например, срез [3:20:5]
означает выборку значений с индексами 3, 8, 13 и 18.
При этом любой из трех параметров среза может быть опущен и вместо соответствующего параметра будет выбрано некое значение по умолчанию:
- Умолчательный
START
означает «от начала списка» - Умолчательный
STOP
означает «до конца списка включительно» - Умолчательный
STEP
означает «брать каждый элемент»
Вот несколько примеров с разными наборами параметров:
[:]
или[::]
— весь список[::2]
— нечетные по порядку элементы[1::2]
— четные по порядку элементы[::-1]
— все элементы в обратном порядке[5:]
— все элементы, начиная с шестого[:5]
— все элементы, не доходя до шестого[-2:1:-1]
— все элементы от предпоследнего до третьего в обратном порядке. Во всех случаях выборки от большего индекса к меньшему нужно указывать шаг
Срезы могут работать в двух режимах: собственно выборка и присваивание.
Выборка элементов
Срезы-выборки работают со списками, кортежами, строками. Результатом применения выборки всегда становится новое значение соответствующего типа — список, кортеж, строка:
'hello'[2:] # 'llo' (1, "foo", True, None)[2:] # (True, None) [1, 2, 3, 4, 5][2:] # [3, 4, 5]
Сразу сделаем несколько замечаний по использованию выборок:
- Кортежи чаще всего содержат разнородные элементы, поэтому срезы для них менее полезны, чем распаковка и перепаковка: тяжело удерживать в голове типы элементов вместе с индексами
- При выборке по срезу
[:]
создается новая копия списка, поэтому именно так обычно список и копируют - Срез порождает новый список или кортеж, но для каждого выбранного элемента копируется только ссылка
Присваивание срезу
В отличие от строк и кортежей списки могут изменяться по месту. Одним из вариантов модификации является присваивание срезу. Срезу с указанным шагом можно присвоить список, содержащий соответствующее количество новых элементов:
l = [1, 2, 3, 4, 5, 6] l[::2] = [0, 0, 0] print(l) # => [0, 2, 0, 4, 0, 6]
Если вы попробуете присвоить срезу с шагом неверное количество элементов, то получите ошибку:
l = [1, 2, 3, 4] l[::2] = [5, 6, 7] # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # ValueError: attempt to assign sequence of size 3 to extended slice of size 2
Если срез непрерывный, то есть шаг не указан и индексы идут подряд, то свободы нам дается больше. Такому срезу можно присвоить как больше элементов — тогда список вырастет, так и меньше, что приведет к урезанию списка:
l = [1, 2, 3] l[2:] = [4, 5] print(l) # => [1, 2, 4, 5] l[1:-1] = [100] print(l) # => [1, 100, 5] l[:] = [] print(l) # => []
Сначала список растет, потом уменьшается, а под конец вообще становится пустым — и все с помощью компактного, но мощного синтаксиса срезов.
Срезы-значения
Хоть срезы и имеют специальную поддержку со стороны синтаксиса, но мы можем создавать и использовать срезы сами по себе — как обычные значения.
Значение среза можно сконструировать с помощью функции slice
:
first_two = slice(2) each_odd = slice(None, None, 2) print(each_odd) # => slice(None, None, 2) l = [1, 2, 3, 4, 5] print(l[first_two]) # => [1, 2] print(l[each_odd]) # => [1, 3, 5]
Функция slice
принимает от одного до трех параметров — те самые START
, STOP
и STEP
. При вызове функции с одним параметром, функция вызывается с параметром STOP
.
Если вы хотите пропустить один из параметров, то подставьте вместо него None
. Также None
можно использовать и в записи срезов в квадратных скобках — там он так же будет означать пропуск значения.
На месте параметров среза могут быть любые выражения, лишь бы эти выражения вычислялись в целые числа или None
.
Соотношение START
и STOP
В срезе элемент с индексом STOP
не попадает в выборку, в отличие от элемента с индексом START
.
У такого поведения есть одна особенность. Какой бы неотрицательный индекс n
мы ни выбрали, для любого списка будет соблюдаться указанное равенство:
l == l[:n] + l[n:]
Посмотрим на такой пример:
s = 'Hello!' print(s[:2] + s[2:]) # => 'Hello!' print(s[:4] + s[4:]) # => 'Hello!' print(s[:0] + s[0:] == s) # => True print(s[:100] + s[100:] == s) # => True
Предыдущий разделСледующий раздел