我正在通过切片处理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 4的5.6. Sequence Types中的已定义(感谢elethan)但 em>为什么在转让时会被认为是可取的。
注意:我理解检索是如何工作的,并且可以看到如何与分配保持一致,但我正在寻找一个引用的原因,为什么分配给切片会在这个行为中表现出来办法。 l[100:]
[]
在l[100:] = ['foo']
之后立即l[3:]
返回['bar']
l[3:] = ['bar']
len(l)
strdup()
如果您不了解strtok()
,则会感到惊讶,特别是如果您正在关注Python的EAFP idiom。
答案 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)。
因此:如果切片的起始索引大于或等于序列的长度,则结果切片的行为就好像它是从序列末尾开始的零宽度切片。即:如果indices
,a >= len(l)
在内置类型上的行为类似于l[a:]
。对于每个分配,检索和删除都是如此。
这是可取的,因为它不需要任何例外。 l[len(l):len(l)]
方法不需要处理任何异常 - 对于长度为slice.indices
的序列,l
将始终生成可用于slice.indices(l)
的索引(start, end, stride)
任何分配,检索和删除,并保证start
和end
都是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
。并且鉴于上述其他一切,如果要保持切片分配和切片检索之间的一致性,这正是人们应该期待的。