为什么Python允许序列的切片索引超出范围?


76

因此,我只是遇到了我觉得很奇怪的Python功能,并希望对此进行一些澄清。

以下数组操作在一定程度上是有意义的:

p = [1,2,3]
p[3:] = [4] 
p = [1,2,3,4]

我想这实际上只是将这个值附加到末尾,对吗?
为什么我可以这样做?

p[20:22] = [5,6]
p = [1,2,3,4,5,6]

更是如此:

p[20:100] = [7,8]
p = [1,2,3,4,5,6,7,8]

这似乎是错误的逻辑。看来这应该引发错误!

有什么解释吗?
-这只是Python所做的奇怪的事情吗?
-有目的吗?
-还是我在想这个错误的方式?


2
在其他语言中,我总是到处写这种东西:if i > sequence.length(): return sequence.slice(0, sequence.length()) else sequence.slice(0, n)这与sequence[:n]在Python中使用完全相同,它为您节省了if语句和2个对的调用length
Bakuriu

4
顺便说一句。您可以将切片视为“集合”。p[20:22]索引在20到22之间的所有元素的序列也是如此。空集是有效集。这p[20]与断言断言存在索引为20的元素的方式不同。因此,在查找元素与切片之间进行范围检查的差异反映了两种不同的含义。
Bakuriu

2
我认为这是一个更广泛的问题,有关为什么在Python中允许在不同长度的序列切片中添加序列以及它的好处是什么。另一个问题根本没有解决该问题的分配部分。它只是在谈论切片。
Akaisteph7 '19

Answers:


79

有关超范围索引的部分问题

切片逻辑会自动将索引剪切到序列的长度。

为方便起见,允许切片索引扩展到结束点。必须对每个表达式进行范围检查,然后手动调整限制,这很痛苦,因此Python会为您完成。

考虑一个用例,它只希望显示一条短信的前50个字符。

简单方法(Python现在做什么):

preview = msg[:50]

或艰难的方式(自己检查极限):

n = len(msg)
preview = msg[:50] if n > 50 else msg

手动实现用于调整终点的逻辑将很容易忘记,很容易出错(在两个地方更新50),比较罗,,而且速度很慢。Python将该逻辑移至简洁,自动,快速和正确的内部。这是我喜欢Python的原因之一:-)

有关分配长度与输入长度不匹配的部分问题

OP还希望了解允许分配的理由,例如p[20:100] = [7,8],分配目标的长度(80)与替换数据长度(2)不同。

用字符串类比看动机最容易。考虑,"five little monkeys".replace("little", "humongous")。请注意,目标“小”只有六个字母,“硕大的”只有九个字母。我们可以对列表做同样的事情:

>>> s = list("five little monkeys")
>>> i = s.index('l')
>>> n = len('little')
>>> s[i : i+n ] = list("humongous")
>>> ''.join(s)
'five humongous monkeys'

这一切都归结为便利。

在引入copy()clear()方法之前,这些方法曾经很流行:

s[:] = []           # clear a list
t = u[:]            # copy a list

即使是现在,我们也使用它来在过滤时更新列表:

s[:] = [x for x in s if not math.isnan(x)]   # filter-out NaN values

希望这些实际例子能很好地说明切片为何如此有效。


2
“即使是现在,我们也会在过滤[使用示例s[:]]时使用它来更新列表。” —您能否解释一下为什么要在此使用列表s[:] =,而不仅仅是s =?我从未见过有人s[:] =在行的上下文中使用您所写的内容。否则,好答案!
Quuxplusone

10
@Quuxplusone:切片分配变异列表已经被引用s; 使用s = 重新绑定 s来引用新列表。如果可以通过多个名称访问该列表,并且希望所有名称都可见该突变,则需要进行切片分配。同样,如果s是全局的,则重新分配s将需要global声明,但是即使没有该global语句,切片分配也将具有类似的效果。
丹尼尔·普赖登

24

文档有您的答案:

s[i:j]si到的切片j(注释(4))

(4)的切片sij被定义为物品的索引序列k,使得i <= k < j如果ij大于 len(s),使用len(s)。如果i省略或None使用0。如果j 省略或None使用len(s)。如果i大于或等于 j,则切片为空。

文档IndexError确认此行为:

例外 IndexError

当序列下标超出范围时引发。(切片索引被无提示地截断以使其落在允许的范围内;如果索引不是整数,TypeError则引发。)

本质上,诸如此类的东西p[20:100]被简化为p[len(p):len(p]p[len(p):len(p]在列表末尾是一个空片,向其分配列表将修改列表末尾以包含所述列表。因此,它的工作方式类似于追加/扩展原始列表。

此行为与将列表分配给原始列表中任意位置的空片时的行为相同。例如:

In [1]: p = [1, 2, 3, 4]

In [2]: p[2:2] = [42, 42, 42]

In [3]: p
Out[3]: [1, 2, 42, 42, 42, 3, 4]

3
我不认为OP在询问切片的工作原理,而是在询问设计选择的依据。
Primusa

2
@Primusa -我相信他们会问两个。这解释了how,这是一个很好的了解,因为它解释了为什么行为没有中断。该原因可能是埋在邮件列表中的某个地方之一的深处。
gddc

好的答案,但这不能解释为什么新数字会附加到列表的末尾。
阿提拉格

1
@Atirag我为此添加了一个简短的说明。
iz_

1
@Atirag索引与切片非常不同;索引总是指值。
iz_
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.