Понимание обозначения среза - PullRequest
2785 голосов
/ 04 февраля 2009

Мне нужно хорошее объяснение (ссылки плюс) на нотации Python.

Мне эта запись нуждается в небольшом подборе.

Выглядит очень мощно, но я не совсем понял, где это.

Ответы [ 31 ]

3784 голосов
/ 04 февраля 2009

Все довольно просто:

a[start:stop]  # items start through stop-1
a[start:]      # items start through the rest of the array
a[:stop]       # items from the beginning through stop-1
a[:]           # a copy of the whole array

Существует также значение step, которое можно использовать с любым из вышеперечисленных:

a[start:stop:step] # start through not past stop, by step

Ключевым моментом, который следует помнить, является то, что значение :stop представляет первое значение, которое не в выбранном срезе. Таким образом, разница между stop и start заключается в количестве выбранных элементов (если step равно 1, по умолчанию).

Другая особенность заключается в том, что start или stop может быть отрицательным числом, что означает, что оно отсчитывается от конца массива, а не от начала. Итак:

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

Аналогично, step может быть отрицательным числом:

a[::-1]    # all items in the array, reversed
a[1::-1]   # the first two items, reversed
a[:-3:-1]  # the last two items, reversed
a[-3::-1]  # everything except the last two items, reversed

Python добр к программисту, если в нем меньше элементов, чем вы просите. Например, если вы запрашиваете a[:-2] и a содержит только один элемент, вы получите пустой список вместо ошибки. Иногда вы предпочитаете ошибку, поэтому вы должны знать, что это может произойти.

Отношение к slice() объекту

Оператор среза [] фактически используется в приведенном выше коде с объектом slice() с использованием записи : (которая действительна только в пределах []), т.е.

a[start:stop:step]

эквивалентно:

a[slice(start, stop, step)]

Объекты среза также ведут себя немного по-разному в зависимости от количества аргументов, аналогично range(), т. Е. Поддерживаются как slice(stop), так и slice(start, stop[, step]). Чтобы пропустить указание заданного аргумента, можно использовать None, например, a[start:] эквивалентно a[slice(start, None)] или a[::-1] эквивалентно a[slice(None, None, -1)].

Хотя нотация на основе : очень полезна для простого среза, явное использование объектов slice() упрощает программную генерацию среза.

487 голосов
/ 04 февраля 2009

Об этом рассказывается в руководстве по Python (прокрутите немного вниз, пока не дойдете до части о нарезке).

Диаграмма ASCII также полезна для запоминания работы срезов:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

Один из способов помнить, как работают срезы, - думать, что индексы указывают между символами, левый край первого символа нумеруется 0. Затем правый край последнего символа строки n имеет индекс n .

369 голосов
/ 04 февраля 2009

Перечисление возможностей, допускаемых грамматикой:

>>> seq[:]                # [seq[0],   seq[1],          ..., seq[-1]    ]
>>> seq[low:]             # [seq[low], seq[low+1],      ..., seq[-1]    ]
>>> seq[:high]            # [seq[0],   seq[1],          ..., seq[high-1]]
>>> seq[low:high]         # [seq[low], seq[low+1],      ..., seq[high-1]]
>>> seq[::stride]         # [seq[0],   seq[stride],     ..., seq[-1]    ]
>>> seq[low::stride]      # [seq[low], seq[low+stride], ..., seq[-1]    ]
>>> seq[:high:stride]     # [seq[0],   seq[stride],     ..., seq[high-1]]
>>> seq[low:high:stride]  # [seq[low], seq[low+stride], ..., seq[high-1]]

Конечно, если (high-low)%stride != 0, то конечная точка будет немного ниже, чем high-1.

Если stride отрицательно, порядок немного меняется, так как мы ведем обратный отсчет:

>>> seq[::-stride]        # [seq[-1],   seq[-1-stride],   ..., seq[0]    ]
>>> seq[high::-stride]    # [seq[high], seq[high-stride], ..., seq[0]    ]
>>> seq[:low:-stride]     # [seq[-1],   seq[-1-stride],   ..., seq[low+1]]
>>> seq[high:low:-stride] # [seq[high], seq[high-stride], ..., seq[low+1]]

Расширенные срезы (с запятыми и эллипсами) в основном используются только специальными структурами данных (например, NumPy); базовые последовательности их не поддерживают.

>>> class slicee:
...     def __getitem__(self, item):
...         return repr(item)
...
>>> slicee()[0, 1:2, ::5, ...]
'(0, slice(1, 2, None), slice(None, None, 5), Ellipsis)'
269 голосов
/ 19 января 2011

Ответы выше не обсуждают назначение срезов. Чтобы понять назначение срезов, полезно добавить еще одну концепцию к искусству ASCII:

                +---+---+---+---+---+---+
                | P | y | t | h | o | n |
                +---+---+---+---+---+---+
Slice position: 0   1   2   3   4   5   6
Index position:   0   1   2   3   4   5

>>> p = ['P','y','t','h','o','n']
# Why the two sets of numbers:
# indexing gives items, not lists
>>> p[0]
 'P'
>>> p[5]
 'n'

# Slicing gives lists
>>> p[0:1]
 ['P']
>>> p[0:2]
 ['P','y']

Одна эвристика заключается в том, что для среза от нуля до n подумайте: «ноль - это начало, начните с начала и возьмите n элементов в списке».

>>> p[5] # the last of six items, indexed from zero
 'n'
>>> p[0:5] # does NOT include the last item!
 ['P','y','t','h','o']
>>> p[0:6] # not p[0:5]!!!
 ['P','y','t','h','o','n']

Другая эвристика: «для любого среза замените начало на ноль, примените предыдущую эвристику, чтобы получить конец списка, а затем сосчитайте первое число обратно, чтобы отрезать элементы от начала»

>>> p[0:4] # Start at the beginning and count out 4 items
 ['P','y','t','h']
>>> p[1:4] # Take one item off the front
 ['y','t','h']
>>> p[2:4] # Take two items off the front
 ['t','h']
# etc.

Первое правило назначения слайсов состоит в том, что поскольку срез возвращает список, назначение слайсов требует список (или другой итеративный):

>>> p[2:3]
 ['t']
>>> p[2:3] = ['T']
>>> p
 ['P','y','T','h','o','n']
>>> p[2:3] = 't'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable

Второе правило назначения слайсов, которое вы также можете видеть выше, заключается в том, что любая часть списка, возвращаемая при индексации слайсов, это та же часть, которая изменяется при назначении слайсов:

>>> p[2:4]
 ['T','h']
>>> p[2:4] = ['t','r']
>>> p
 ['P','y','t','r','o','n']

Третье правило назначения слайсов состоит в том, что назначенный список (повторяемый) не должен иметь одинаковую длину; Индексированный фрагмент просто вырезается и заменяется массово тем, что ему назначено:

>>> p = ['P','y','t','h','o','n'] # Start over
>>> p[2:4] = ['s','p','a','m']
>>> p
 ['P','y','s','p','a','m','o','n']

Самая хитрая часть, к которой нужно привыкнуть, - это назначение пустым слайсам. Используя эвристику 1 и 2, легко обойти , индексировать пустой фрагмент:

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []

И затем, как только вы это увидели, назначение среза пустому срезу также имеет смысл:

>>> p = ['P','y','t','h','o','n']
>>> p[2:4] = ['x','y'] # Assigned list is same length as slice
>>> p
 ['P','y','x','y','o','n'] # Result is same length
>>> p = ['P','y','t','h','o','n']
>>> p[3:4] = ['x','y'] # Assigned list is longer than slice
>>> p
 ['P','y','t','x','y','o','n'] # The result is longer
>>> p = ['P','y','t','h','o','n']
>>> p[4:4] = ['x','y']
>>> p
 ['P','y','t','h','x','y','o','n'] # The result is longer still

Обратите внимание, что, поскольку мы не меняем второе число среза (4), вставленные элементы всегда располагаются прямо напротив «o», даже когда мы назначаем пустой срез. Таким образом, позиция для назначения пустых срезов является логическим расширением позиций для непустых назначений срезов.

Немного резервного копирования, что произойдет, если вы продолжите нашу процессию подсчета начала среза?

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []

С нарезкой, как только вы закончите, вы закончите; это не начинает разрезать назад. В Python вы не получите отрицательных шагов, если вы явно не попросите их, используя отрицательное число.

>>> p[5:3:-1]
 ['n','o']

Есть некоторые странные последствия для правила «как только вы закончите, вы закончите»:

>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []
>>> p[6]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

На самом деле, по сравнению с индексацией, нарезка Python причудливо защищена от ошибок:

>>> p[100:200]
 []
>>> p[int(2e99):int(1e99)]
 []

Иногда это может пригодиться, но может привести к несколько странному поведению:

>>> p
 ['P', 'y', 't', 'h', 'o', 'n']
>>> p[int(2e99):int(1e99)] = ['p','o','w','e','r']
>>> p
 ['P', 'y', 't', 'h', 'o', 'n', 'p', 'o', 'w', 'e', 'r']

В зависимости от вашего приложения, это может ... или не может ... быть тем, на что вы надеялись!

<Ч />

Ниже приведен текст моего первоначального ответа. Это было полезно для многих людей, поэтому я не хотел его удалять.

>>> r=[1,2,3,4]
>>> r[1:1]
[]
>>> r[1:1]=[9,8]
>>> r
[1, 9, 8, 2, 3, 4]
>>> r[1:1]=['blah']
>>> r
[1, 'blah', 9, 8, 2, 3, 4]

Это также может прояснить разницу между нарезкой и индексацией.

227 голосов
/ 12 июля 2014

Объяснить обозначение среза Python

Короче говоря, двоеточия (:) в нижнем индексном обозначении (subscriptable[subscriptarg]) обозначают срезы, которые имеют необязательные аргументы, start, stop, step:

sliceable[start:stop:step]

Python-срез - это вычислительно быстрый способ методического доступа к частям ваших данных. На мой взгляд, чтобы быть даже программистом среднего уровня на Python, это один из аспектов языка, с которым необходимо быть знакомым.

Важные определения

Для начала давайте определим несколько терминов:

start: начальный индекс среза, он будет включать элемент с этим индексом, если он не совпадает с stop , по умолчанию 0, т.е. первый индекс. Если оно отрицательное, это означает, что нужно начинать n элементов с конца.

stop: конечный индекс среза, он не включает элемент с этим индексом, по умолчанию длина отрезанной последовательности, то есть до и включительно конец.

шаг: величина, на которую увеличивается индекс, по умолчанию равен 1. Если он отрицательный, вы пересекаете итеративное в обратном порядке.

Как работает индексирование

Вы можете сделать любое из этих положительных или отрицательных чисел. Значение положительных чисел прямолинейно, но для отрицательных чисел, как и для индексов в Python, отсчет от конца для обратного отсчета для start и stop и для шаг , вы просто уменьшаете свой индекс. Этот пример из учебного руководства по документации , но я немного изменил его, чтобы указать, на какой элемент в последовательности ссылается каждый индекс:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
   0   1   2   3   4   5 
  -6  -5  -4  -3  -2  -1

Как работает нарезка

Чтобы использовать нотацию среза с последовательностью, которая ее поддерживает, необходимо включить хотя бы одно двоеточие в квадратные скобки, которые следуют за последовательностью (что на самом деле реализует метод __getitem__ последовательности, согласно данным Python модель .)

Обозначение среза работает следующим образом:

sequence[start:stop:step]

И помните, что существуют значения по умолчанию для start , stop и step , поэтому для доступа к значениям по умолчанию просто пропустите аргумент.

Обозначение фрагмента для получения последних девяти элементов из списка (или любой другой последовательности, поддерживающей его, например строки) будет выглядеть так:

my_list[-9:]

Когда я вижу это, я читаю часть в скобках как «9-й от конца до конца». (На самом деле, я мысленно сокращаю его как «-9, на»)

Пояснение:

Полная запись

my_list[-9:None:None]

и для замены значений по умолчанию (на самом деле, когда step отрицательно, stop по умолчанию равно -len(my_list) - 1, поэтому None для останова просто означает, что он переходит к тому конечному шагу, к которому он приводит):

my_list[-9:len(my_list):1]

двоеточие , : - это то, что говорит Python, что вы даете ему фрагмент, а не обычный индекс. Вот почему идиоматический способ создания мелкой копии списков в Python 2 -

list_copy = sequence[:]

И очистить их можно с помощью:

del my_list[:]

(Python 3 получает метод list.copy и list.clear.)

Когда step отрицательно, значения по умолчанию для start и stop изменяются

По умолчанию, когда аргумент step пуст (или None), он присваивается +1.

Но вы можете передать отрицательное целое число, и список (или большинство других стандартных разрезаемых элементов) будет разрезан от конца до начала.

Таким образом, отрицательный срез изменит значения по умолчанию для start и stop!

Подтверждение этого в источнике

Мне нравится поощрять пользователей читать исходные тексты и документацию. Исходный код для объектов слайса и эта логика находятся здесь . Сначала мы определяем, является ли step отрицательным:

 step_is_negative = step_sign < 0;

Если это так, нижняя граница равна -1, что означает, что мы нарезаем весь путь вплоть до начала, а верхняя граница - это длина минус 1, что означает, что мы начинаем с конца. (Обратите внимание, что семантика этого -1 отличается от -1, что пользователи могут передавать индексы в Python, указывающие последний элемент.)

if (step_is_negative) {
    lower = PyLong_FromLong(-1L);
    if (lower == NULL)
        goto error;

    upper = PyNumber_Add(length, lower);
    if (upper == NULL)
        goto error;
}

В противном случае step является положительным, и нижняя граница будет равна нулю, а верхняя граница (до которой мы дойдем, но не включая) длины нарезанного списка.

else {
    lower = _PyLong_Zero;
    Py_INCREF(lower);
    upper = length;
    Py_INCREF(upper);
}

Тогда нам может потребоваться применить значения по умолчанию для start и stop - значение по умолчанию для start вычисляется как верхняя граница, когда step отрицательно:

if (self->start == Py_None) {
    start = step_is_negative ? upper : lower;
    Py_INCREF(start);
}

и stop, нижняя граница:

if (self->stop == Py_None) {
    stop = step_is_negative ? lower : upper;
    Py_INCREF(stop);
}

Дайте своим ломтикам описательное имя!

Может оказаться полезным отделить формирование среза от передачи его методу list.__getitem__ ( - это то, что делают квадратные скобки ). Даже если вы не новичок в этом, он делает ваш код более читабельным, чтобы другие, которым, возможно, придется читать ваш код, могли более легко понять, что вы делаете.

Однако вы не можете просто присвоить переменной целые числа, разделенные двоеточиями. Вам необходимо использовать объект среза:

last_nine_slice = slice(-9, None)

Второй аргумент, None, является обязательным, поэтому первый аргумент интерпретируется как start аргумент , в противном случае это будет stop аргумент .

Затем вы можете передать объект слайса в вашу последовательность:

>>> list(range(100))[last_nine_slice]
[91, 92, 93, 94, 95, 96, 97, 98, 99]

Интересно, что диапазоны также берут ломтики:

>>> range(100)[last_nine_slice]
range(91, 100)

Особенности памяти:

Поскольку срезы списков Python создают новые объекты в памяти, следует помнить еще одну важную функцию itertools.islice. Обычно вам нужно перебирать фрагмент, а не просто создавать его статически в памяти. islice идеально подходит для этого. Предостережение: он не поддерживает отрицательные аргументы для start, stop или step, поэтому, если это проблема, вам может потребоваться рассчитать индексы или заранее изменить итерацию заранее.

length = 100
last_nine_iter = itertools.islice(list(range(length)), length-9, None, 1)
list_last_nine = list(last_nine_iter)

и сейчас:

>>> list_last_nine
[91, 92, 93, 94, 95, 96, 97, 98, 99]

Тот факт, что срезы списков делают копию, является особенностью самих списков. Если вы нарезаете сложные объекты, такие как Pandas DataFrame, он может вернуть представление оригинала, а не его копию.

138 голосов
/ 04 февраля 2009

И пара вещей, которые не были сразу очевидны для меня, когда я впервые увидел синтаксис среза:

>>> x = [1,2,3,4,5,6]
>>> x[::-1]
[6,5,4,3,2,1]

Простой способ изменить последовательность!

И если вы хотите, по какой-то причине, каждый второй элемент в обратной последовательности:

>>> x = [1,2,3,4,5,6]
>>> x[::-2]
[6,4,2]
93 голосов
/ 22 октября 2012

В Python 2.7

Нарезка в Python

[a:b:c]

len = length of string, tuple or list

c -- default is +1. The sign of c indicates forward or backward, absolute value of c indicates steps. Default is forward with step size 1. Positive means forward, negative means backward.

a --  When c is positive or blank, default is 0. When c is negative, default is -1.

b --  When c is positive or blank, default is len. When c is negative, default is -(len+1).

Понимание назначения индекса очень важно.

In forward direction, starts at 0 and ends at len-1

In backward direction, starts at -1 and ends at -len

Когда вы говорите [a: b: c], вы говорите, в зависимости от знака c (вперед или назад), начинайте с a и заканчивайте на b (исключая элемент с индексом b). Используйте приведенное выше правило индексации и помните, что вы найдете только элементы в этом диапазоне:

-len, -len+1, -len+2, ..., 0, 1, 2,3,4 , len -1

Но этот диапазон продолжается в обоих направлениях бесконечно:

...,-len -2 ,-len-1,-len, -len+1, -len+2, ..., 0, 1, 2,3,4 , len -1, len, len +1, len+2 , ....

Например:

             0    1    2   3    4   5   6   7   8   9   10   11
             a    s    t   r    i   n   g
    -9  -8  -7   -6   -5  -4   -3  -2  -1

Если ваш выбор a, b и c позволяет перекрываться с указанным выше диапазоном, когда вы пересекаете, используя правила для a, b, c выше, вы либо получите список с элементами (затронутыми во время обхода), либо вы получите пустой список.

И последнее: если a и b равны, то также вы получите пустой список:

>>> l1
[2, 3, 4]

>>> l1[:]
[2, 3, 4]

>>> l1[::-1] # a default is -1 , b default is -(len+1)
[4, 3, 2]

>>> l1[:-4:-1] # a default is -1
[4, 3, 2]

>>> l1[:-3:-1] # a default is -1
[4, 3]

>>> l1[::] # c default is +1, so a default is 0, b default is len
[2, 3, 4]

>>> l1[::-1] # c is -1 , so a default is -1 and b default is -(len+1)
[4, 3, 2]


>>> l1[-100:-200:-1] # Interesting
[]

>>> l1[-1:-200:-1] # Interesting
[4, 3, 2]


>>> l1[-1:-1:1]
[]


>>> l1[-1:5:1] # Interesting
[4]


>>> l1[1:-7:1]
[]

>>> l1[1:-7:-1] # Interesting
[3, 2]

>>> l1[:-2:-2] # a default is -1, stop(b) at -2 , step(c) by 2 in reverse direction
[4]
91 голосов
/ 06 сентября 2011

Нашел этот великий стол в http://wiki.python.org/moin/MovingToPythonFromOtherLanguages

Python indexes and slices for a six-element list.
Indexes enumerate the elements, slices enumerate the spaces between the elements.

Index from rear:    -6  -5  -4  -3  -2  -1      a=[0,1,2,3,4,5]    a[1:]==[1,2,3,4,5]
Index from front:    0   1   2   3   4   5      len(a)==6          a[:5]==[0,1,2,3,4]
                   +---+---+---+---+---+---+    a[0]==0            a[:-2]==[0,1,2,3]
                   | a | b | c | d | e | f |    a[5]==5            a[1:2]==[1]
                   +---+---+---+---+---+---+    a[-1]==5           a[1:-1]==[1,2,3,4]
Slice from front:  :   1   2   3   4   5   :    a[-2]==4
Slice from rear:   :  -5  -4  -3  -2  -1   :
                                                b=a[:]
                                                b==[0,1,2,3,4,5] (shallow copy of a)
58 голосов
/ 19 февраля 2009

После его использования я понимаю, что самое простое описание - это то же самое, что и аргументы в цикле for ...

(from:to:step)

Любые из них являются необязательными:

(:to:step)
(from::step)
(from:to)

Тогда для отрицательного индексирования нужно, чтобы вы добавили длину строки к отрицательным индексам, чтобы понять это.

Это все равно работает для меня ...

44 голосов
/ 29 марта 2012

Мне легче вспомнить, как это работает, и затем я могу выяснить любую конкретную комбинацию старт / стоп / шаг.

Поучительно сначала понять range():

def range(start=0, stop, step=1):  # Illegal syntax, but that's the effect
    i = start
    while (i < stop if step > 0 else i > stop):
        yield i
        i += step

Начинаются с start, с шагом step, не достигают stop. Очень просто.

При отрицательном шаге следует помнить, что stop всегда является исключенным концом, независимо от того, выше он или ниже. Если вы хотите, чтобы один и тот же срез находился в обратном порядке, гораздо проще сделать инверсию отдельно: например, 'abcde'[1:-2][::-1] отсекает один символ слева, два справа, затем переворачивает. (См. Также reversed().)

Секвенирование последовательности такое же, за исключением того, что оно сначала нормализует отрицательные индексы и никогда не может выходить за пределы последовательности:

TODO : В приведенном ниже коде была ошибка "никогда не выходить за пределы последовательности", когда abs (шаг)> 1; Я думаю Я исправил это, чтобы быть правильным, но это трудно понять.

def this_is_how_slicing_works(seq, start=None, stop=None, step=1):
    if start is None:
        start = (0 if step > 0 else len(seq)-1)
    elif start < 0:
        start += len(seq)
    if not 0 <= start < len(seq):  # clip if still outside bounds
        start = (0 if step > 0 else len(seq)-1)
    if stop is None:
        stop = (len(seq) if step > 0 else -1)  # really -1, not last element
    elif stop < 0:
        stop += len(seq)
    for i in range(start, stop, step):
        if 0 <= i < len(seq):
            yield seq[i]

Не беспокойтесь о деталях is None - просто помните, что пропуск start и / или stop всегда делает правильную вещь, давая вам всю последовательность.

Сначала нормализация отрицательных индексов позволяет независимо начинать и / или останавливать отсчет с конца: 'abcde'[1:-2] == 'abcde'[1:3] == 'bc' несмотря на range(1,-2) == []. Нормализацию иногда считают «по модулю длины», но обратите внимание, что она добавляет длину только один раз: например, 'abcde'[-53:42] это просто целая строка.

...