为什么通过切片分配超出列表末尾而不引发IndexError?

时间:2016-11-12 01:09:04

标签: python list variable-assignment slice

我正在通过切片处理sparse list implementation和最近实施的任务。这让我发现了Python内置list实现中I find suprising的一些行为。

给定空list并通过切片分配:

>>> l = []
>>> l[100:] = ['foo']

我原本期望IndexError来自list因为实现它的方式意味着无法从指定的索引中检索项目::

>>> l[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
甚至无法从指定的切片中检索

'foo'

>>> l = []
>>> l[100:] = ['foo']
>>> l[100:]
[]

l[100:] = ['foo'] 附加到list(即此作业后的l == ['foo']),并且似乎自the BDFL's initial version以来一直表现出来。我无法在任何地方找到此功能(*),但CPython和PyPy都以这种方式运行。

按索引分配会引发错误:

>>> l[100] = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range

那么为什么通过切片分配超过list的末尾不会引发IndexError(或者其他错误,我猜)?

为了澄清前两条评论,此问题专门针对作业,而不是检索 cf。 Why substring slicing index out of range works in Python?

当我明确指定索引100时,投入到猜测并将'foo'分配到l 索引0 的诱惑不会跟随通常的Python之禅。

考虑分配远离初始化并且索引是变量的情况。调用者无法再从指定位置检索数据。

list结束之前分配给切片与上述示例的行为略有不同:

>>> l = [None, None, None, None]
>>> l[3:] = ['bar']
>>> l[3:]
['bar']

(*)官方文档中Note 45.6. Sequence Types中的已定义(感谢elethan)但 em>为什么在转让时会被认为是可取的。

注意:我理解检索是如何工作的,并且可以看到如何与分配保持一致,但我正在寻找一个引用的原因,为什么分配给切片会在这个行为中表现出来办法。 l[100:] []l[100:] = ['foo']之后立即l[3:]返回['bar'] l[3:] = ['bar'] len(l) strdup()如果您不了解strtok(),则会感到惊讶,特别是如果您正在关注Python的EAFP idiom

2 个答案:

答案 0 :(得分:11)

让我们看看实际发生了什么:

>>> l = []
>>> l[100:] = ['foo']
>>> l[100:]
[]
>>> l
['foo']

因此,作业实际上是成功的,并且该项目被放入列表中,作为第一项。

为什么会发生这种情况是因为索引位置的100:转换为slice对象:slice(100, None, None)

>>> class Foo:
...     def __getitem__(self, i):
...         return i
... 
>>> Foo()[100:]
slice(100, None, None)

现在,slice类有一个方法indices(我无法在线找到它的Python文档),当给定一个序列的长度时,它会给{{1}根据该序列的长度进行调整。

(start, stop, stride)

因此,当此切片应用于长度为0的序列时,其行为与切片检索的切片>>> slice(100, None, None).indices(0) (0, 0, 1) 完全相同,例如当slice(0, 0, 1)为空序列时,而不是foo[100:]抛出错误,它的行为就像请求foo一样 - 这将导致检索时出现空切片。

现在,当l是具有超过100个元素的序列时,使用foo[0:0:1]时,setter代码应该可以正常工作。为了使它在那里工作,最简单的是重新发明轮子,并且只使用上面的l[100:]机制。作为一个缺点,它现在在边缘情况下看起来有点特殊,但切片分配到超出范围的切片&#34;将被放置在当前序列的末尾。 (但是,事实证明,CPython代码中的代码重用很少; list_ass_slice基本上复制了所有这些索引处理even though it would also be available via slice object C-API)。

因此:如果切片的起始索引大于或等于序列的长度,则结果切片的行为就好像它是从序列末尾开始的零宽度切片。即:如果indicesa >= len(l)在内置类型上的行为类似于l[a:]。对于每个分配,检索和删除都是如此。

这是可取的,因为它不需要任何例外l[len(l):len(l)]方法不需要处理任何异常 - 对于长度为slice.indices的序列,l将始终生成可用于slice.indices(l)的索引(start, end, stride)任何分配,检索和删除,并保证startend都是0 <= v <= len(l)

答案 1 :(得分:3)

对于索引,如果给定的索引超出范围,则必须引发错误 ,因为没有可接受的默认值可以返回。 (返回None是不可接受的,因为None可能是序列的有效元素。)

相比之下,对于切片,如果任何索引超出范围,则不需要引发错误,因为可以将空序列作为默认值返回。并且它也是可取的,因为它提供了一致的方式来引用元素之间和序列末端之外的子序列(因此允许插入)。

Sequence Types Notes中所述,如果切片的起始值或结束值大于len(seq),则会使用len(seq)

所以给定a = [4, 5, 6],表达式a[3:]a[100:]都指向列表中最后一个元素后面的空子序列。但是,在使用这些表达式进行切片分配后,它们可能不再引用相同的内容,因为列表的长度可能已经更改。

因此,在对象a[3:] = [7]之后,切片a[3:]将返回[7]。但是在取消a[100:] = [8]之后,切片a[100:]仍将返回[],因为len(a)仍然小于100。并且鉴于上述其他一切,如果要保持切片分配和切片检索之间的一致性,这正是人们应该期待的。