Таинственное взаимодействие между границами среза Python и "шагом"

Я понимаю, что с учетом итерации, такой как

>>> it = [1, 2, 3, 4, 5, 6, 7, 8, 9]

Я могу превратить его в список и отрезать концы в произвольных точках, например,

>>> it[1:-2]
[2, 3, 4, 5, 6, 7]

или отмените его с помощью

>>> it[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1]

или объединить два с

>>> it[1:-2][::-1]
[7, 6, 5, 4, 3, 2]

Однако попытка выполнить это за одну операцию приводит к некоторым результатам, которые меня озадачат:

>>> it[1:-2:-1] 
[]
>>>> it[-1:2:-1] 
[9, 8, 7, 6, 5, 4]
>>>> it[-2:1:-1]
[8, 7, 6, 5, 4, 3]

Только после долгих проб и ошибок, я получаю то, что я ищу:

>>> it[-3:0:-1]
[7, 6, 5, 4, 3, 2]

Это заставляет мою голову болеть (и не может помочь читателям моего кода):

>>> it[-3:0:-1] == it[1:-2][::-1]
True

Как я могу это понять? Должен ли я даже обдумывать такие вещи?


FWYW, мой код делает много усечений, реверсирования и перечисления итераций, и я искал что-то более быстрое и четкое (да, не смейтесь), чем list(reversed(it[1:-2])).

Ответы

Ответ 1

Это происходит потому, что во фрагменте, например -

list[start:stop:step]

start включительно, итоговый список начинается с индекса start.

stop эксклюзивный, то есть результирующий список содержит только элементы до stop - 1 (а не элемент в stop).

Итак, для вашего случая it[1:-2] - 1 является включительным, это означает, что результат среза начинается с индекса 1, тогда как -2 является исключительным, поэтому последний элемент индекса среза будет из индекса -3.

Следовательно, если вы хотите, чтобы это было отменено, вам нужно было бы сделать it[-3:0:-1] - только тогда -3 будет включен в результат нарезки, а результат нарезанного будет идти до индекса 1.

Ответ 2

Важными вещами, которые нужно понимать в ваших срезах, являются

  • Начало будет включено в срез

  • Стоп будет НЕ включен в срез

  • Если вы хотите обрезать назад, значение шага должно быть отрицательным.

В основном заданный вами диапазон является полуоткрытым (полузакрытым) диапазоном.


Когда вы говорите it[-3:0:-1], вы фактически начинаете с третьего элемента со спины, до тех пор, пока мы не достигнем 0 (не считая нуля), шаг за шагом за один раз назад.

>>> it[-3:0:-1]
[7, 6, 5, 4, 3, 2]

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

>>> it[len(it)-3 : 0 : -1]
[7, 6, 5, 4, 3, 2]

Ответ 3

Я думаю, что два других ответа устраняют использование slicing и дают более четкое представление о том, как работают его параметры.


Но, поскольку ваш вопрос также включает в себя удобочитаемость, что, не забывайте, является большим фактором, особенно в Python. Я хотел бы указать, как вы можете немного улучшить его, назначив slice() объектам переменным, удалив все эти жесткокодированные : разделенные числа.

Ваш объект обрезания и реверсивного среза может, альтернативно, быть закодирован с использованием подразумевающего имя:

rev_slice = slice(-3, 0, -1)

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

it[rev_slice]  # [7, 6, 5, 4, 3, 2] 

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

Ответ 4

Почему бы не создать функцию для удобочитаемости:

def listify(it, start=0, stop=None, rev=False):
    if stop is None:
        the_list = it[start:]
    else:
        the_list = it[start:stop]
    if rev:
        return the_list[::-1]
    else:
        return the_list

listify(it, start=1, stop=-2)  # [2, 3, 4, 5, 6, 7]
listify(it, start=1, stop=-2, rev=True)  # [7, 6, 5, 4, 3, 2]

Ответ 5

Хороший способ интуитивно понять синтаксис разреза Python - увидеть, как он сопоставляется с соответствующим циклом C for.

Срез как

x[a:b:c]

предоставляет те же элементы, что и

for (int i = a; i < b; i += c) {
  ...
}

Частными случаями являются только значения по умолчанию:

  • a по умолчанию 0
  • b по умолчанию len(x)
  • c по умолчанию - 1

Плюс еще один специальный случай:

  • Если c отрицательно, то a и b меняются местами, а < инвертируется на >