Полное руководство по slice в Python
В Python некоторые объекты, такие как str
s или list
s, можно нарезать . Например, вы можете получить первый элемент списка или строки с помощью
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]`.
Поскольку мы используем None
s, объект 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
Вернуться на верх