Полное руководство по slice в Python
В Python некоторые объекты, такие как strs или lists, можно нарезать . Например, вы можете получить первый элемент списка или строки с помощью
my_list = [1,2,3]
print(my_list[0]) # 1
my_string = "Python"
print(my_string[0]) # P
Python использует квадратные скобки ([ и ]) для доступа к отдельным элементам объектов, которые могут быть разложены на части.
Однако, внутри этих квадратных скобок есть нечто большее, чем просто доступ к отдельным элементам:
Отрицательная индексация
Возможно, вы уже знаете, что в Python можно использовать отрицательные индексы следующим образом:
my_list = list("Python")
print(my_list[-1])
Что-то вроде my_list[-1] представляет последний элемент списка, my_list[-2] представляет второй последний элемент и так далее.
Колонна
Что делать, если вы хотите получить более одного элемента из списка? Скажем, вам нужно получить все от начала до конца, кроме самого последнего. В Python, без проблем:
my_list = list("Python")
print(my_list[0:-1])
Или, что если вам нужен каждый четный элемент вашего списка, т.е. элемент 0, 2 и т.д.? Для этого нам нужно перейти от первого элемента к последнему, но пропустить каждый второй элемент. Мы можем записать это так:
my_list = list("Python")
print(my_list[0:len(my_list):2]) # ['P', 't', 'o']
Объект slice
За кулисами индекс, который мы используем для доступа к отдельным элементам спископодобного объекта, состоит из трех значений: (start, stop, step). Эти объекты называются объектами слайсов и могут быть созданы вручную с помощью встроенной функции slice.
Мы можем проверить, действительно ли они одинаковы:
my_list = list("Python")
start = 0
stop = len(my_list)
step = 2
slice_object = slice(start, stop, step)
print(my_list[start:stop:step] == my_list[slice_object]) # True
Посмотрите на график выше. Буква P является первым элементом в нашем списке, поэтому он может быть проиндексирован 0 (см. цифры в зеленых квадратиках). Список имеет длину 6, и поэтому первый элемент может быть альтернативно проиндексирован -6 (отрицательная индексация показана в синих квадратиках).
Числа в зеленой и синей клетках обозначают отдельные элементы списка. Теперь посмотрите на числа в оранжевых ячейках. Они определяют индексы  срезов  списка. Если мы используем индексы среза start и stop, то каждый элемент  между  этими числами будет охвачен срезом. Некоторые примеры:
"Python"[0:1] # P
"Python"[0:5] # Pytho
Это простой способ запомнить, что значение start является инклюзивным, а значение end - эксклюзивным.
Разумные значения по умолчанию
В большинстве случаев, вы хотите, чтобы slice ваши list по
- начиная с 0
- останавливаясь в конце
- шаг шириной 1
Таким образом, это значения по умолчанию, и их можно опустить в нашем синтаксисе ::
print(my_list[0:-1] == my_list[:-1])
print(my_list[0:len(my_list):2] == my_list[::2])
Технически, когда мы опускаем число между двоеточиями, опущенные числа будут иметь значение None.
В свою очередь, объект slice заменит None на
- 0для начального значения
- len(list)для значения остановки
- 1для значения шага
Однако, если значение step отрицательное, None заменяются на
- -1для начального значения
- -len(list) - 1для значения остановки
Например, "Python"[::-1] технически то же самое, что "Python"[-1:-7:-1]
Специальный случай: Копия
Для нарезки существует специальный случай, который иногда можно использовать как сокращение:
Если вы используете только значения по умолчанию, т.е. my_list[:], это даст вам точно такие же элементы:
my_list = list("Python")
my_list_2 = my_list[:]
print(my_list==my_list_2)
Элементы в списке действительно одинаковы. Однако объект списка не одинаков. Мы можем проверить это с помощью встроенного модуля id:
print(id(my_list))
print(id(my_list_2))
Обратите внимание, что каждая операция среза возвращает новый объект. Копия нашей последовательности создается при использовании только [:].
Вот два фрагмента кода, иллюстрирующие разницу:
a = list("Python")
b = a
a[-1] = "N"
print(a)
# ['P', 'y', 't', 'h', 'o', 'N']
print(b)
# ['P', 'y', 't', 'h', 'o', 'N']
a = list("Python")
b = a[:]
a[-1] = "N"
print(a)
# ['P', 'y', 't', 'h', 'o', 'N']
print(b)
# ['P', 'y', 't', 'h', 'o', 'n']
Примеры
Некоторые часто используемые примеры:
| Пример использования | Код Python | 
|---|---|
| Каждый элемент | без среза, или [:]для копии | 
| Каждый второй элемент | [::2](четный) или[1::2](нечетный) | 
| Каждый элемент, кроме первого | [1:] | 
| Каждый элемент, кроме последнего | [:-1] | 
| Каждый элемент, кроме первого и последнего | [1:-1] | 
| Каждый элемент в обратном порядке | [::-1] | 
| Каждый элемент, кроме первого и последнего, в обратном порядке | [-2:0:-1] | 
| Каждый второй элемент, кроме первого и последнего, в обратном порядке | [-2:0:-2] | 
Задания
p = list("Python")
# ['P', 'y', 't', 'h', 'o', 'n']
p[1:-1]
# ['y', 't', 'h', 'o']
p[1:-1] = 'x'
print(p)
['P', 'x', 'n']
p = list("Python")
p[1:-1] = ['x'] * 4
p
# ['P', 'x', 'x', 'x', 'x', 'n']
Понимание цикла
Каждый объект slice в Python имеет метод indices. Этот метод возвращает пару (start, end, step), с помощью которых можно построить цикл, эквивалентный операции нарезки. Звучит сложно? Давайте рассмотрим подробнее:
Начнем с последовательности:
sequence = list("Python")
Затем создадим объект slice. Возьмем каждый второй элемент, т.е. [::2].
my_slice = slice(None, None, 2) # equivalent to `[::2]`.
Поскольку мы используем Nones, объект slice должен вычислить фактические значения index на основе длины нашей последовательности. Поэтому, чтобы получить тройной индекс, нам нужно передать длину в метод indices, как показано ниже:
indices = my_slice.indices(len(sequence))
Это даст нам тройку (0, 6, 2). Теперь мы можем воссоздать цикл следующим образом:
sequence = list("Python")
start = 0
stop =  6
step =  2
i = start
while i != stop:
    print(sequence[i])
    i = i+step
Это дает доступ к тем же элементам нашего списка, что и сам slice.
Сделать собственные классы разрезаемыми
Python не был бы Python, если бы вы не могли использовать объект среза в своих собственных классах. Что еще лучше, срезы не обязательно должны быть числовыми значениями. Мы можем построить адресную книгу, которая может быть нарезана по алфавитным индексам.
import string
class AddressBook:
    def __init__(self):
        self.addresses = []
    
    def add_address(self, name, address):
        self.addresses.append((name, address))
    def get_addresses_by_first_letters(self, letters):
        letters = letters.upper()
        return [(name, address) for name, address in self.addresses if any(name.upper().startswith(letter) for letter in letters)]
    def __getitem__(self, key):
        if isinstance(key, str):
            return self.get_addresses_by_first_letters(key)
        if isinstance(key, slice):
            start, stop, step = key.start, key.stop, key.step
            letters = (string.ascii_uppercase[string.ascii_uppercase.index(start):string.ascii_uppercase.index(stop)+1:step])
            return self.get_addresses_by_first_letters(letters)
address_book = AddressBook()
address_book.add_address("Sherlock Holmes",       "221B Baker St., London")
address_book.add_address("Wallace and Gromit",    "62 West Wallaby Street, Wigan, Lancashire")
address_book.add_address("Peter Wimsey",          "110a Piccadilly, London")
address_book.add_address("Al Bundy",              "9764 Jeopardy Lane, Chicago, Illinois")
address_book.add_address("John Dolittle",         "Oxenthorpe Road, Puddleby-on-the-Marsh, Slopshire, England")
address_book.add_address("Spongebob Squarepants", "124 Conch Street, Bikini Bottom, Pacific Ocean")
address_book.add_address("Hercule Poirot",        "Apt. 56B, Whitehaven Mansions, Sandhurst Square, London W1")
address_book.add_address("Bart Simpson",          "742 Evergreen Terrace, Springfield, USA")
print(string.ascii_uppercase)
print(string.ascii_uppercase.index("A"))
print(string.ascii_uppercase.index("Z"))
print(address_book["A"])
print(address_book["B"])
print(address_book["S"])
print(address_book["A":"H"])
Пояснение
Метод get_addresses_by_first_letters
    def get_addresses_by_first_letters(self, letters):
        letters = letters.upper()
        return [(name, address) for name, address in self.addresses if any(name.upper().startswith(letter) for letter in letters)]
Этот метод фильтрует все адреса, принадлежащие name, начинающиеся с любой буквы в аргументе letters. Сначала мы делаем функцию нечувствительной к регистру, преобразуя наши letters в верхний регистр. Затем мы используем понимание списка  над нашим внутренним списком addresses. Условие внутри списка проверяет, совпадает ли любая из предоставленных букв с первой буквой соответствующего значения name.
Метод __getitem__
Чтобы сделать наши AddressBook объекты sliceable, нам нужно переписать магический double underscore метод Python __getitem__.
    def __getitem__(self, key):
        if isinstance(key, str):
            return self.get_addresses_by_first_letters(key)
        if isinstance(key, slice):
            start, stop, step = key.start, key.stop, key.step
            letters = (string.ascii_uppercase[string.ascii_uppercase.index(start):string.ascii_uppercase.index(stop)+1:step])
            return self.get_addresses_by_first_letters(letters)
Сначала мы проверяем, является ли наш ключ str. Это будет так, если мы обращаемся к нашему объекту с одной буквой в квадратных скобках, как это: address_book["A"]. Для этого тривиального случая мы можем просто вернуть любой адрес, имя которого начинается с данной буквы.
Интересно, когда key является объектом slice. Например, доступ типа address_book["A":"H"] будет соответствовать этому условию. Сначала мы определяем все буквы в алфавитном порядке между A и H. Модуль string в Python перечисляет все (латинские) буквы в string.ascii_uppercase. Мы используем slice для извлечения букв между заданными буквами. Обратите внимание на +1 во втором параметре slice. Таким образом, мы гарантируем, что последняя буква будет включительной, а не исключительной.
После того, как мы определили все буквы в нашей последовательности, мы используем get_addresses_by_first_letters, о котором мы уже говорили. Это дает нам желаемый результат.
https://bas.codes/posts/python-slicing
Back to Top