Индексы и срезы
Сегодня мы поговорим об операциях взятия индекса и среза.
Взятие элемента по индексу
Как и в других языках программирования, взятие по индексу:
File
Как и во многих других языках, нумерация элементов начинается с нуля. При попытке доступа к несуществующему индексу возникает исключение IndexError.
В данном примере переменная a являлась списком, однако взять элемент по индексу можно и у других типов: строк, кортежей.
В Python также поддерживаются отрицательные индексы, при этом нумерация идёт с конца, например:
File
Срезы
В Python, кроме индексов, существуют ещё и срезы.
item[START:STOP:STEP] — берёт срез от номера START, до STOP (не включая его), с шагом STEP. По умолчанию START = 0, STOP = длине объекта, STEP = 1. Соответственно, какие-нибудь (а возможно, и все) параметры могут быть опущены.
Также все эти параметры могут быть и отрицательными:
В последнем примере получился пустой список, так как START < STOP, а STEP отрицательный. То же самое произойдёт, если диапазон значений окажется за пределами объекта:
Для вставки кода на Python в комментарий заключайте его в теги
- Модуль csv - чтение и запись CSV файлов
- Создаём сайт на Django, используя хорошие практики. Часть 1: создаём проект
- Онлайн-обучение Python: сравнение популярных программ
- Книги о Python
- GUI (графический интерфейс пользователя)
- Курсы Python
- Модули
- Новости мира Python
- NumPy
- Обработка данных
- Основы программирования
- Примеры программ
- Типы данных в Python
- Видео
- Python для Web
- Работа для Python-программистов
- Сделай свой вклад в развитие сайта!
- Самоучитель Python
- Карта сайта
- Отзывы на книги по Python
- Реклама на сайте
Индексы списков и срез в Python
Для любого итерируемого объекта (например, строки, списка и т. Д.) Python позволяет срезать и вернуть подстроку или подсписок своих данных.
Формат для среза:
iterable_name[start:stop:step]
- start первый индекс среза. По умолчанию 0 (индекс первого элемента)
- stop последний индекс среза. По умолчанию len (индекс последнего элемента)
- step шаг размера среза.(лучше объясняется в примере ниже)
a = "abcdef" a # так же как a[:] или a[::] если используются индексы по умолчанию всех трех индексов >>>Out: 'abcdef' a[-1] >>>Out: 'f' a[:] >>>Out: 'abcdef' a[::] >>>Out: 'abcdef' a[3:] # от элемента с индексом 3 до конца(размер шага имеет значение по умолчанию) >>>Out: 'def' a[:4] # от начала (по умолчанию 0) до позиции 4 (не включая ее) >>>Out: 'abcd' a[2:4] # от позиции 2 до позиции 4 (не включая ее) >>>Out: 'cd'Кроме того, любой из вышеперечисленных срезов может использоваться с определенным размером шага:
a[::2] # каждый второй элемент >>>Out: 'ace' a[1:4:2] # от индекса 1 до индекса 4 (исключая), каждый второй элемент) >>>Out: 'bd'Индексы могут быть отрицательными, и в этом случае они вычисляются с конца последовательности
a[:-1] #от нулевого индекса (по умолчанию) до 2ого индекса с конца (последний элемент - 1) >>>Out: 'abcde' a[:-2] # от нулевого индекса (по умолчанию) до 3его индекса с конца (последний элемент - 2) >>>Out: 'abcd' a[-1:] # от последнего элемента до конца (по умолчанию len()) >>>Out: 'f'Размер шага также может быть отрицательным, в этом случае срез будет перебирать список в обратном порядке:
a[3:1:-1] # от индекса 2 до None (по умолчанию), в обратном порядке) >>>Out: 'dc'Эта конструкция полезна для обращения итеративного
a[::-1] # от последнего элемента (по умолчанию len()-1), до первого в обратном порядке (-1)) >>>Out: 'fedcba'Обратите внимание на то, что для отрицательных шагов по умолчанию end_index не None
a[5:None:-1] # эквивалентно a[::-1] >>>Out: 'fedcba' a[5:0:-1] # от последнего элемента (индекс 5) до второго элемента (индекс 1) >>>Out: 'fedcb'Создание мелкой копии массива
Быстрый способ сделать копию массива (в отличие от присвоения переменной с другой ссылкой на исходный массив):
Давайте рассмотрим синтаксис. [:] Означает , что start , end , и slice все опущены. Они по умолчанию равны 0 , len(arr) , и 1 , соответственно, что означает, что мы запрашиваем не будет иметь все элементы arr от начала до самого конца. На практике это выглядит примерно так:
arr = ['a', 'b', 'c'] copy = arr[:] arr.append('d') print(arr) >>>Out: ['a', 'b', 'c', 'd'] print(copy) >>>Out: ['a', 'b', 'c']Как вы можете видеть, arr.append('d') добавил d к arr , но copy осталась неизменной!
Обратите внимание, что это делает неполную копию, и copy не идентичен arr.copy() .
Разворот объекта
Вы можете использовать срезы для легкого разворота str , list или tuple (или в основном любой набор объектов , который реализует срез с параметром шага). Вот пример разворота строки, хотя это в равной степени относится и к другим типам, перечисленным выше:
s = 'reverse me!' s[::-1] >>>Out: '!em esrever'Давайте быстро посмотрим на синтаксис. [::-1] означает , что срез должен быть с самого начала до конца строки (потому что start и end опущены) и шаг -1 означает , что он должен двигаться через колонну в обратном направлении.
Индексирование пользовательских классов: __getitem__, __setitem__ и __delitem__
class MultiIndexingList: def __init__(self, value): self.value = value def __repr__(self): return repr(self.value) def __getitem__(self, item): if isinstance(item, (int, slice)): return self.__class__(self.value[item]) return [self.value[i] for i in item] def __setitem__(self, item, value): if isinstance(item, int): self.value[item] = value elif isinstance(item, slice): raise ValueError('Невозможно интерпретировать срез с множественным индексированием') else: for i in item: if isinstance(i, slice): raise ValueError('Невозможно интерпретировать срез с множественным индексированием') self.value[i] = value def __delitem__(self, item): if isinstance(item, int): del self.value[item] elif isinstance(item, slice): del self.value[item] else: if any(isinstance(elem, slice) for elem in item): raise ValueError('Невозможно интерпретировать срез с множественным индексированием') item = sorted(item, reverse=True) for elem in item: del self.value[elem]Это позволяет нарезать и индексировать для доступа к элементу:
a = MultiIndexingList([1,2,3,4,5,6,7,8]) a >>>Out: [1, 2, 3, 4, 5, 6, 7, 8] a[1,5,2,6,1] >>>Out: [2, 6, 3, 7, 2] a[4, 1, 5:, 2, ::2] >>>Out: [5, 2, [6, 7, 8], 3, [1, 3, 5, 7]] # 4|1-|----5:---|2-|-----::2-----Во время установки и удаления элементов допускает только запятые целочисленной индексации (без нарезки):
a[4] = 1000 a >>>Out: [1, 2, 3, 4, 1000, 6, 7, 8] a[2,6,1] = 100 >>>Out: [1, 100, 100, 4, 1000, 6, 100, 8] del a[5] a >>>Out: [1, 100, 100, 4, 1000, 100, 8] del a[4,2,5] a >>>Out: [1, 100, 4, 8]Назначение среза
Еще одна полезная функция, использующая срезы- это назначение срезов. Python позволяет назначать новые фрагменты для замены старых фрагментов списка за одну операцию.
Это означает, что если у вас есть список, вы можете заменить несколько членов в одном назначении:
lst = [1, 2, 3] lst[1:3] = [4, 5] print(lst) >>>Out: [1, 4, 5]Назначение также не должно совпадать по размеру, поэтому, если вы хотите заменить старый срез новым, отличающимся по размеру, вы можете:
lst = [1, 2, 3, 4, 5] lst[1:4] = [6] print(lst) >>>Out: [1, 6, 5]Также возможно использовать известный синтаксис среза для таких вещей, как замена всего списка:
lst = [1, 2, 3] lst[:] = [4, 5, 6] print(lst) >>>Out: [4, 5, 6]Или только два последних члена:
lst = [1, 2, 3] lst[-2:] = [4, 5, 6] print(lst) >>>Out: [1, 4, 5, 6]Базовое индексирование
В списках Python первый элемент в списке можно получить по индексу 0
arr = ['a', 'b', 'c', 'd'] print(arr[0]) >>>Out: 'a'Вы можете получить доступ к второму элементу в списке по индексу 1 , третий элемент по индексу 2 и так далее:
print(arr[1]) >>>Out: 'b' print(arr[2]) >>>Out: 'c'Вы также можете использовать отрицательные индексы для доступа к элементам в конце списка. например. индекс -1 даст вам последний элемент списка и индекс -2 даст вам второй до последнего элемента списка:
print(arr[-1]) >>>Out: 'd' print(arr[-2]) >>>Out: 'c'Если вы пытаетесь получить доступ к индексу , которого нет в списке, будет вызвана ошибка IndexError :
print arr[6] Traceback (most recent call last): File "", line 1, in IndexError: list index out of rangeСрезы в Python
К вашему вниманию гайд по слайсингу (AKA суши-оператор, слайс, срез)! Наверно, все пишущие на питоне знают о синтаксисе вырезания частей последовательности - s[a:b] . Сейчас же мы рассмотрим как самые очевидные, так и менее известные факты об операции получения среза.
(Но прежде чем начнём, нужно уточнить, что в Python как и во многих других языках последний элемент не включается в срезы и диапазоны, что соответствует индексации с нуля. seq[начало:конец:шаг] (Для вычисления данного выражения Python вызывает seq.__getitem__ ) - берёт срез от НАЧАЛО, до КОНЕЦ (не включая его), с шагом ШАГ. По умолчанию НАЧАЛО = 0, КОНЕЦ = длине объекта, ШАГ = 1. Соответственно, какие-нибудь, а возможно и все ( seq[:], поговорим об этом позже ) параметры могут быть опущены).
Получение по срезу
"Python"
P
Y
T
H
O
N
>>> some_str = "Python" >>> some_str[:1] # Задаём конечным индексом 1, а т.к. конец не включается, мы получим первый элемент последовательности ('P'). 'P' >>> PYTH = slice(0, 4) # Вместо того чтоб загромождать код вшитыми диапазонами, мы можем проименовать их (например для упрощения разбора накладной). >>> some_str[PYTH] 'Pyth' >>> some_str[::2] # Шагаем через букву. 'Pto'Немного о NumPy
Оператор [] может принимать несколько индексов или срезов, разделённых запятыми a[m:n, k:l] . В основном это используется в NumPy для получения двумерного среза и a[i, j] для получения одного элемента двумерного массива. Соответственно для вычисления a[i, j] Python вызывает a.__getitem__((i, j)) . Кстати, т.к. " . " (единственный экземпляр класса ellipsis) распознаётся как лексема, то мы его можем использовать так же и в срезах. Применение этому нашлось в том же NumPy для сокращённого создания среза двумерного массива. Детальнее - https://scipy.github.io/old-wiki/pages/Tentative_NumPy_Tutorial.
Удаление по срезу
Так же мы можем удалять элементы последовательности по срезу с учётом того, что она поддерживает удаление элементов (изменяема))). Приведу пример с рантайм классом SupportsDeletion .
>>> import typing >>> @typing.runtime_checkable >>> class SupportsDeletion(typing.Protocol): >>> def __delitem__(self, key: typing.Any) -> typing.Any: >>> . >>> for builtin_type in (list, dict, str, tuple): >>> print(builtin_type.__name__, "->", isinstance(builtin_type, SupportsDeletion)) list -> True dict -> True str -> False tuple -> False>>> seq = [1, 2, 3, 4] >>> del seq[:3] # Удаляем первых три элемента последовательности. >>> seq [4]Лайфхаки 🙂
№1 Можно перевернуть последовательность, если запросить срез seq[::-1]
>>> seq = (1, 2, 3, 4, 5, 6) >>> seq[::-1] (6, 5, 4, 3, 2, 1)№2 Как удалить все элементы списка с помощью слайса, не разрушая сам обьект-список? (Разумеется, тот же результат можно получить вызвав метод .clear() у списка, но мы же сейчас про слайсы говорим + его нет во второй версии питона).
>>> seq = [1, 2, 3, 4, 5, 6] >>> id(seq) 2071031395200 >>> del seq[:] >>> id(seq) 2071031395200 >>> seq []№3 Помимо очистки списков, нарезку также можно использовать для замены всех элементов списка, не создавая новый объект-список. Это отличная сокращённая запись для очистки списка и затем повторного его заполнения вручную:
>>> seq = [1, 2, 3, 4, 5, 6, 7] >>> original_list = seq >>> seq[:] = [1, 2, 3] >>> seq [1, 2, 3] >>> original_list [1, 2, 3] >>> original_list is seq TrueПриведённый выше пример кода заменил все элементы в seq , но не уничтожил и воссоздал список как таковой. По этой причине старые ссылки на оригинальный объект-список по-прежнему действительны.
№4 Создание мелких копий существующих списков:
>>> seq = [1, 2, 3, 4, 5, 6, 7] >>> copied_list = seq[:] >>> copied_list [1, 2, 3, 4, 5, 6, 7] >>> copied_list is seq FalseСоздание мелкой копии означает, что копируется только структура элементов, но не сами элементы. Обе копии списка совместно используют одинаковые экземпляры отдельных элементов.
Если же вам необходимо продублировать абсолютно всё, включая и элементы, то необходимо создать глубокую копию списка ( copy.deepcopy(x) ). Для этой цели пригодится встроенный модуль в Python copy.
Ключевые выводы
Слайсы полезны не только для отбора элементов внутри последовательности, а могут также использоваться для очистки, реверсирования и копирования списков.
Срезы — Python: Списки
Работать с одиночными элементами вы уже умеете. Настало время перейти к очень интересному инструменту, который Python предоставляет для работы с целыми подмножествами элементов списка: к так называемым срезам (slices).
Синтаксис описания срезов
Срезы встроены в язык и снабжены своим собственным синтаксисом — настолько широко они используются. Срез записывается так же, как записывается обращение к элементу списка по индексу:
[START:STOP:STEP]some_list
Всего у среза три параметра:
- START — индекс первого элемента в выборке
- STOP — индекс элемента списка, перед которым срез должен закончиться. Сам элемент с индексом STOP не будет входить в выборку
- STEP — шаг прироста выбираемых индексов
Математически говоря, во множество будут входить индексы элементов, которые будут выбраны:
Например, срез [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 "", line 1, in # 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
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
