77

所以我刚刚发现了一个在我看来像是一个奇怪的 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 做的一件奇怪的事情吗?
-有目的吗?
- 还是我想错了?

4

2 回答 2

81

关于超出范围指数的部分问题

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

为方便起见,允许切片索引延伸过去的端点。必须对每个表达式进行范围检查然后手动调整限制会很痛苦,因此 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")。请注意,目标“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

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

于 2019-02-10T06:16:45.867 回答
25

文档有你的答案:

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

s(4) from ito的切片j被定义为索引为 的项k序列i <= k < j如果ij大于 len(s),请使用len(s)。如果i省略 or None,请使用0. 如果j 省略 or 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]
于 2019-02-10T06:09:28.767 回答