Таинственное взаимодействие между границами среза 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
меняются местами, а <
инвертируется на >