Полное руководство по 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

 

Python Slicing

Посмотрите на график выше. Буква 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

Вернуться на верх