Вопрос: Понимание нотации среза Python


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

Для меня эта нотация требует немного поднять.

Он выглядит чрезвычайно мощным, но я не совсем обнял его.


2222


источник


Ответы:


Это довольно просто:

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

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

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

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

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

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содержит только один элемент, вместо ошибки вы получаете пустой список. Иногда вы предпочитаете ошибку, поэтому вы должны знать, что это может произойти.


3016



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

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

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

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


384



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

>>> 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 `item`
...
>>> slicee()[0, 1:2, ::5, ...]
'(0, slice(1, 2, None), slice(None, None, 5), Ellipsis)'

303



В приведенных выше ответах не обсуждается назначение среза:

>>> 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]

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


196



Объяснить нотацию фрагмента Python

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

sliceable[start:stop:step]

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

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

Прежде всего, давайте определим несколько терминов:

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

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

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

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

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

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

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

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

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

sequence[start:stop:step]

И помните, что существуют значения по умолчанию для Начало , стоп , а также шаг , поэтому для доступа к значениям по умолчанию просто оставьте аргумент.

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

my_list[-9:]

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

Объяснение:

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

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,

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

Таким образом, отрицательный срез изменит значения по умолчанию для 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, он может вернуть представление на оригинале, а не на копию.


173



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

>>> 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]

122



Нашел этот отличный стол в 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)

83



In Python 2.7

Slicing in 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).

Understanding index assignment is very important.

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

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

When you say [a:b:c], you are saying depending on the sign of c (forward or backward), start at a and end at b (excluding element at bth index). Use the indexing rule above and remember you will only find elements in this range:

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

But this range continues in both directions infinitely:

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

For example:

             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

If your choice of a, b, and c allows overlap with the range above as you traverse using rules for a,b,c above you will either get a list with elements (touched during traversal) or you will get an empty list.

One last thing: if a and b are equal, then also you get an empty list:

>>> 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]

83