Ошибка списка Python: [:: - 1] шаг на [: -1] срез
Мне показалось, что я понял основы перебора списков в python, но получал непредвиденную ошибку при использовании отрицательного шага на срезе следующим образом:
>>> a = list(range(10))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[:-1]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> a[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[:-1:-1]
[]
(Обратите внимание, что это выполняется в Python 3.5)
Почему не делается обратный шаг [: - 1: -1] через срез [: - 1] таким же образом, как и весь список с помощью [:: - 1]?
Я понимаю, что вы также можете использовать list.reverse(), но лучше понять базовую функциональность среза python.
Ответы
Ответ 1
Первый -1
в a[:-1:-1]
не означает, что вы думаете.
В разрезе отрицательные начальные/конечные индексы не интерпретируются буквально. Вместо этого они используются для удобного обращения к концу списка (т.е. Относятся к len(a)
). Это происходит независимо от направления нарезки.
Это означает, что
a[:-1:-1]
эквивалентно
a[:len(a)-1:-1]
При опускании во время реверсивного среза начальный индекс по умолчанию равен len(a)-1
, что делает эквивалентным значение
a[len(a)-1:len(a)-1:-1]
Это всегда дает пустой список, так как начальный и конечный индексы являются одинаковыми, а конечный индекс является исключительным.
Чтобы отрезать в обратном порядке до нулевого элемента и включить его, вы можете использовать любую из следующих обозначений:
>>> a[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[:None:-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[:-len(a)-1:-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Ответ 2
При вводе [1, 2, 3, ...][1:4:1]
он совпадает с [1, 2, 3, ...][slice(1, 4, 1)]
. Таким образом, 1:4:1
является сокращением для объекта slice
. slice
подпись slice(stop)
или slice(start, stop[, step])
, и вы также можете использовать None
для аргументов.
:: -> slice(None, None, None)
:4 -> slice(4)
# and so on
Предположим, что мы получили [a: b: c]
. Правила для индексов будут следующими:
- Проверяется первый
c
. Значение по умолчанию +1
, знак c
указывает направление вперед или назад. Абсолютное значение c
указывает размер шага.
- Проверяется
a
. Когда c
является положительным или None
, значение по умолчанию для a
равно 0
. Если c
отрицательный, по умолчанию для a
есть -1
.
- Наконец
b
проверяется. Если c
положительный или None
, значение по умолчанию для b
равно len
. Если c
является отрицательным значением по умолчанию для b
является -(len+1)
.
Примечание 1. Вырожденные фрагменты в Python обрабатываются изящно:
- слишком большой или слишком маленький индекс заменяется на
len
или 0
.
- верхняя граница, меньшая нижней границы, возвращает пустой список или строку или что-то еще (для положительного
c
).
Примечание 2. Грубо говоря, Python берет элементы, а это условие (a < b) if (c > 0) else (a > b)
равно True
(обновление a += c
на каждом шаге). Кроме того, все отрицательные индексы заменяются на len - index
.
Если вы объедините эти правила и примечания, будет понятно, почему вы получили пустой список. В вашем случае:
In[1]: [1, 2, 3, 4, 5, 6][:-1:-1] # `c` is negative so `a` is -1 and `b` is -1
Out[1]: []
# it is the same as:
In[2]: [1, 2, 3, 4, 5, 6][-1: -1: -1] # which will produce you an empty list
Out[2]: []
Существует очень хорошая дискуссия о нотации фрагмента: Объяснить нотацию фрагмента Python!
Ответ 3
Мне обычно полезно нарезать a range
-объект (это возможно только в python3 - в python2 range
выдает list
и xrange
нельзя отрезать), если мне нужно увидеть, какой индексы используются для списка заданной длины:
>>> range(10)[::-1]
range(9, -1, -1)
>>> range(10)[:-1]
range(0, 9)
И в вашем последнем случае:
>>> range(10)[:-1:-1]
range(9, 9, -1)
Это также объясняет, что произошло. Первый индекс равен 9, но 9 не ниже индекса остановки 9 (обратите внимание, что в python исключающий индекс исключается), поэтому он останавливается без предоставления какого-либо элемента.
Обратите внимание, что индексирование также может применяться последовательно:
>>> list(range(10))[::-1][:-1] # first reverse then exclude last item.
[9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> list(range(10))[:-1][::-1] # other way around
[8, 7, 6, 5, 4, 3, 2, 1, 0]
Ответ 4
slice
работает аналогично range
тем, что, когда вы делаете аргумент step
отрицательным числом, аргументы start
и stop
работают в противоположном направлении.
>>> list(range(9, -1, -1)) == a[::-1]
True
Некоторые примеры, которые могут помочь сделать это более понятным:
>>> a[6:2:-2]
[6, 4]
>>> a[0:None:1] == a[::]
True
>>> a[-1:None:-1] == a[::-1]
True
>>> a[-2:None:-1] == a[:-1][::-1]
True
Ответ 5
Сначала фрагменты Python кажутся довольно простыми, но их поведение на самом деле довольно сложное (примечания 3 и 5 здесь актуальны). Если у вас есть фрагмент a[i:j:k]
:
- Если
i
или j
являются отрицательными, они ссылаются на индекс с конца a
(поэтому a[-1]
ссылается на последний элемент a
)
-
Если i
или j
не указаны или являются None
, по умолчанию они заканчиваются концами a
, но заканчивается, зависит от знака k
:
- Если
k
положителен, вы нарезаете вперед, поэтому i
становится 0 и j
становится len(a)
-
если k
отрицательный, вы отбрасываете назад, поэтому i
становится len(a)
, а j
становится элементом до начала a
.
NB: j
не может быть заменен на -1, так как это приведет к тому, что Python будет обрабатывать j
как последний элемент a
, а не (несуществующий) элемент до a[0]
. Чтобы получить желаемое поведение, вы должны использовать -len(a)-1
(или -(len(a)+1)
) вместо j
, что означает, что для перехода к a[j]
, срез начинается с последнего элемента a
, идет влево для len(a)
, а затем оставил еще один элемент, заканчивающийся до начала a
и включающий a[0]
в срез.
Следовательно, a[:-1:-1]
означает "перейти от конца a
, который равен a[-1]
(так как i
не указан и k
отрицательный), последнему элементу a
(так как j == -1
), размер шага -1". i
и j
равны - вы начинаете и останавливаете нарезку в одном и том же месте - поэтому выражение оценивается как пустой список.
Чтобы отменить a[:-1]
, вы можете использовать a[-2::-1]
. Таким образом, срез начинается с предпоследнего элемента a[-2]
(поскольку a[:-1]
не включает a[-1]
) и идет назад, пока элемент "до" a[0]
, что означает, что a[0]
включен в срез.
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[:-1]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> a[-2::-1]
[8, 7, 6, 5, 4, 3, 2, 1, 0]