为什么Python切片允许非整数内置类型?

6

我刚刚提高了一个需要支持切片的的测试覆盖率,并且注意到切片可以包含非整型类型:

>>> slice(1, "2", 3.0)
slice(1, '2', 3.0)
>>> sl = slice(1, "2", 3.0)
>>> [1,2,3][sl]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: slice indices must be integers or None or have an __index__ method

这也许只是我在静态类型背景下的理解,但没有 __index__ 的内置类型可以在此处传递而不引发 TypeError,这对我来说似乎很奇怪。为什么会这样?我可以假设允许任意类型是为了支持实现了 __index__ 的类型进行鸭子类型检查吗?缺乏类型检查是否是出于性能原因,以支持最常见的用例?
PEP 357 之前,该示例中的切片是否无效?

切片语法可以有效地描述n维空间中的边界框,这在某些情况下非常方便。自定义类型可以接受这样的切片。 - Kevin
这是一个非常有趣的浮点数使用案例,我之前没有想到过。但为什么允许字符串呢? - shuttle87
1
您还可以在切片中使用省略号Ellipsis - Laurent LAPORTE
@LaurentLAPORTE:你可以使用省略号;虽然我从未见过这样做。在NumPy中,像a[3, ..., 5]这样的东西,...不是切片的一部分。 - Mark Dickinson
@Mark Dickinson:我记不得在哪里看到过了,但省略号只在Numpy中使用;-) - Laurent LAPORTE
2个回答

4

第三方库可能希望为其自己的对象实现切片,核心语言没有理由限制这些第三方库只使用整数或类似整数的对象(即,其类型提供__index__方法)。以下是两个在切片中使用非整数的显着例子:在NumPy中,一些对象接受复杂步进,例如:

>>> import numpy
>>> numpy.mgrid[0:2:5j]
array([ 0. ,  0.5,  1. ,  1.5,  2. ])

在Pandas中,你可以通过label来切片一个SeriesDataframe对象。这个label可以是字符串,也可以是datetime对象。

>>> import pandas
>>> s = pandas.Series(range(4), index=['a', 'b', 'c', 'd'])
>>> s['b':'d']
b    1
c    2
d    3
dtype: int64

因此,当构建一个包含非整数的切片时,核心语言引发异常是没有意义的;这将破坏上述库。相反,实际的切片操作应在切片组件(起始、停止、步骤)不是适当类型时引发异常。


2
我猜测,允许任意类型是为了支持实现__index__方法的类型进行鸭子类型检查,这样做是正确的吗?
在初始化slice对象时,没有实际理由限制传递的类型。正如PEP 357的理由所述,numpy和它使用的数值类型不能从object继承,因此对传递的类型进行严格的issubclass检查将使它们无法用作索引值。因此采用了鸭子类型,如果定义了适当的方法(__index__),则可以使用它们。
请注意,仅在应用切片时(正如您所看到的,在执行__getitem__操作(即list_subscript)时引发了TypeError异常),强制执行此(__index__存在)操作,即当调用PySlice_GetIndicesEx尝试获取您传递的值时。
切片对象初始化程序不对其接受的类型进行任何区分,所有PyObject都可以应用,这可以从其签名中看出。
PyObject *
PySlice_New(PyObject *start, PyObject *stop, PyObject *step)
{
   /* rest omitted */

在PEP 357之前,这个例子中的切片是无效的吗?
我刚刚构建了一个Python 2.4版本并进行了测试(如果我没记错的话,PEP 357出现在2.5中),再次检查参数是否为数字不是在初始化时完成的,而是在调用__getitem__时完成的;唯一不同的是异常消息没有提到__index__ dunder(显然当时不存在)。
Python 2.4 (#1, Sep 11 2016, 18:13:11) 
[GCC 5.4.0 20160609] on linux4
Type "help", "copyright", "credits" or "license" for more information.
>>> s = slice(0, Ellipsis)
>>> [1, 2, 3][s]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: slice indices must be integers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接