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
.
开始
stop
是 exclusive,即结果列表只包含直到 stop - 1
的元素(而不是stop
).
处的元素
所以对于你的情况it[1:-2]
- 1
是 inclusive ,这意味着切片结果从索引 1
开始,而-2
是 exclusive ,因此切片索引的最后一个元素将来自索引 -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
为负数,则 a
和 b
交换并且 <
反转为 >
我理解给定一个可迭代对象,例如
>>> 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
.
stop
是 exclusive,即结果列表只包含直到 stop - 1
的元素(而不是stop
).
所以对于你的情况it[1:-2]
- 1
是 inclusive ,这意味着切片结果从索引 1
开始,而-2
是 exclusive ,因此切片索引的最后一个元素将来自索引 -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
默认为 0b
默认为len(x)
c
默认为 1
再加一个特例:
- 如果
c
为负数,则a
和b
交换并且<
反转为>