6

I've just improving test coverage for a library that has to support slices and I've noticed that slices can contain non-integral types:

>>> 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

This might just be my static-typing background but it seems strange to me that the builtin types without __index__ can be passed in here without a TypeError. Why is this so? Would I be right in assuming that arbitrary types are allowed in order to support duck typing for types that implement __index__? Is the lack of a type check due to performance reasons for the most commonly used cases?

Prior to PEP 357 was the slice in the example invalid?

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
shuttle87
  • 15,466
  • 11
  • 77
  • 106
  • Slice syntax with floats can efficiently describe a bounding box in n-dimensional space, which can be handy in some circumstances. A custom type can accept such slices. – Kevin Sep 11 '16 at 06:07
  • That's a really interesting use case for a float that I hadn't thought of. Why are strings allowed though? – shuttle87 Sep 11 '16 at 06:45
  • 1
    You can also use [Ellipsis](http://stackoverflow.com/questions/772124/what-does-the-python-ellipsis-object-do) in slices. – Laurent LAPORTE Sep 11 '16 at 07:14
  • @LaurentLAPORTE: You *can* use Ellipsis; I've never seen it done, though. In NumPy, in something like `a[3, ..., 5]` , `...` is not part of a slice. – Mark Dickinson Sep 11 '16 at 17:33
  • @Mark Dickinson: I can’t remember where I saw that, but Ellipsis is only used in Numpy ;-) – Laurent LAPORTE Sep 11 '16 at 18:30

2 Answers2

4

Third-party libraries may want to implement slicing for their own objects, and there's no reason for the core language to restrict those third-party libraries to only using integers or integer-like objects (i.e., objects whose type provides the __index__ method) in their slices. Here are two notable examples of packages using non-integers in slicing: in NumPy, some objects accept a complex step, for example:

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

And in Pandas, you can slice a Series or Dataframe object by label. That label could be a string, or a datetime object (for example).

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

So it wouldn't make sense for the core language to raise an exception when constructing a slice containing non-integers; that would break the above libraries. Instead, the actual slicing operation should raise an exception if the slice components (start, stop, step) are not of the appropriate type.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
2

Would I be right in assuming that arbitrary types are allowed in order to support duck typing for types that implement __index__?

There's no actual reason as to why you should restrict the types passed when initializing slice objects. Exactly as stated in the rational for PEP 357, numpy and the numeric types it utilizes can't inherit from object so strict issubclass checks of the type passed will make them unusable as index values. So duck typing is employed, if it defines the appropriate method (__index__), it's usable.

Also take note that this (the presence of __index__) is enforced only when applying the slice (as you saw, the TypeError was raised during the __getitem__, i.e list_subscript ) operation when PySlice_GetIndicesEx is called to try and get the values you passed.

The slice objects initializer makes no discrimination on what type it accepts, all PyObject's can apply as can be seen from its signature:

PyObject *
PySlice_New(PyObject *start, PyObject *stop, PyObject *step)
{
   /* rest omitted */

Prior to PEP 357 was the slice in the example invalid?

I just built a 2.4 version of Python and tested it (PEP 357 appeared in 2.5 if I'm not mistaken), again the check if the arguments are numeric is not done during initialization but when __getitem__ is invoked; the only thing that differs is the exception message which doesn't make any notice about the __index__ dunder (which then obviously didn't exist):

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
Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253