Python list error: [::-1] step on [:-1] slice

Python list error: [::-1] step on [:-1] slice

我以为我了解了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 中的 运行)

为什么 a[:-1:-1] 不以与 a[::-1] 遍历整个列表相同的方式反向遍历 a[:-1] 切片?

我知道您也可以使用 list.reverse(),但我想更好地理解底层的 python 切片功能。

a[:-1:-1]中的第一个-1并不代表你认为的那样。

在切片中,负 start/end 索引不按字面解释。相反,它们用于方便地引用列表的末尾(即它们相对于 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]

Python 的切片初看起来相当简单,但它们的行为实际上是 quite complex(注释 3 和 5 与此处相关)。如果你有一个切片 a[i:j:k]:

  • 如果ij是负数,它们指的是从a末尾开始的索引(所以a[-1]指的是[=14的最后一个元素=])
  • 如果没有指定ij,或者是None,它们默认到a的结尾,但是哪个结束取决于k的符号:

    • 如果 k 为正数,则向前切分,因此 i 变为 0,j 变为 len(a)
    • 如果 k 为负数,则向后切片,因此 i 变为 len(a) 并且 j 变为开始之前的元素a.

      注意: j 不能将替换为-1,因为这样做会导致Python将 j 视为 alast 元素,而不是 a[0] 之前的(不存在的)元素。要获得所需的行为,您必须使用 -len(a)-1(或 -(len(a)+1))代替 j,这意味着要到达 a[j],切片从a,向左移动 len(a) 个元素,然后再向左移动一个元素,在 a 开始之前结束,因此在切片中包含 a[0]

因此,a[:-1:-1]表示"go from the end of a, which is a[-1] (since i is unspecified and k is negative), to the last element of a (since j == -1), with step size of -1"。 ij 是相等的——你在同一个地方开始和停止切片——所以表达式的计算结果是一个空列表。

要反转a[:-1],可以使用a[-2::-1]。这样,切片从倒数第二个元素 a[-2] 开始(因为 a[:-1] 不包括 a[-1])并向后移动直到元素 "before" 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]

当您键入 [1, 2, 3, ...][1:4:1] 时,它与 [1, 2, 3, ...][slice(1, 4, 1)] 相同。所以 1:4:1slice 对象的 shorthand。 slice 签名是 slice(stop)slice(start, stop[, step]),您也可以使用 None 作为参数。

:: -> slice(None, None, None)
:4 -> slice(4)
# and so on

假设我们有[a: b: c]索引的规则如下:

  1. 首先检查c。默认为+1c的符号表示步进的前进或后退方向。 c的绝对值表示步长。
  2. a勾选。当 c 为正数或 None 时,a 的默认值为 0。当 c 为负数时,a 的默认值为 -1.
  3. 终于b被选中了。当 c 为正数或 None 时,b 的默认值为 len。当 c 为负数时 b 的默认值为 -(len+1).

注释 1:Python 中的退化切片被优雅地处理:

  • 太大或太小的索引被替换为len0
  • 小于下限的上限 returns 空列表或字符串或其他任何内容(对于正数 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]: [] 

关于切片符号的讨论非常好:Explain Python's slice notation!

slicerange 的工作方式类似,当您将 step 参数设置为负数时,startstop 参数在相反的方向。

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

我通常发现对 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 中,停止索引是 excluded),因此它在不提供任何元素的情况下停止。

请注意,索引也可以按顺序应用:

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

通俗的理解就是如果最后的a[::-1]-1把字符串反转。

现在

a=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
a[::-1]=[9, 8, 7, 6, 5, 4, 3, 2, 1, 0];

now a[:-1:-1] 中间的 -1 没有任何意义,因为现在它是第一个元素,这将给出一个空列表。 而 a[-1::-1] 为您提供了完整的列表并且很有意义。