Python 的切片边界和 "stride" 之间神秘的相互作用

Mysterious interaction between Python's slice bounds and "stride"

我理解给定一个可迭代对象,例如

>>> 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])) 更快更清晰(是的,不要笑)的东西。

这是因为在像这样的切片中 -

list[start:stop:step]

start 包含 ,结果列表从索引 start.

开始

stopexclusive,即结果列表只包含直到 stop - 1 的元素(而不是stop).

处的元素

所以对于你的情况it[1:-2] - 1inclusive ,这意味着切片结果从索引 1 开始,而-2exclusive ,因此切片索引的最后一个元素将来自索引 -3.

因此,如果您想要相反的结果,则必须执行 it[-3:0:-1] - 只有这样 -3 才会包含在切片结果中,并且切片结果会达到 1索引。

切片中需要理解的重要内容是

  • 开始将包含在切片中

  • 停止将包含在切片中

  • 如果要反向切片,步长值应该是负值。

基本上你指定的范围是半开(半闭)范围。


当你说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]

我认为其他两个答案消除了 slicing 用法的歧义,并更清楚地说明了其参数的工作原理。


但是,由于您的问题还涉及可读性——我们不要忘记, 是一个重要因素,尤其是在 Python 中——我想指出如何通过将 slice() 对象分配给变量从而删除所有对象来稍微改进它那些硬编码 : 分隔的数字。

您的截断和反向切片对象也可以使用隐含名称的用法进行编码:

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

在其他一些类似配置的文件中。然后,您可以在切片操作中以其命名的荣耀使用它,使这个稍微看起来更容易:

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

这可能是一件微不足道的事情,但我认为这可能是值得的。

为什么不为可读性创建一个函数:

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]

直观理解 Python 切片语法的一个好方法是查看它如何映射到相应的 C for 循环。

一片像

x[a:b:c]

为您提供与

相同的元素
for (int i = a; i < b; i += c) {
  ...
}

特殊情况只是默认值:

  • a 默认为 0
  • b 默认为 len(x)
  • c 默认为 1

再加一个特例:

  • 如果 c 为负数,则 ab 交换并且 < 反转为 >