The Slice Object

Ilan Schnell -- December 2019

Presenter Notes

Basics

Python has a very easy and powerful slicing syntax:

>>> s = "Python rocks!"

First six elements:

>>> s[:6]
'Python'

Last six elements:

>>> s[-6:]
'rocks!'

Every other element:

>>> s[::2]
'Pto ok!'

Reverse elements:

>>> s[::-1]
'!skcor nohtyP'

In general, we can index a sequence by index:

>>> s[index]

Or slice:

>>> s[start:stop:step]

(where each slice parameter is optional)

Presenter Notes

Introduction

Let's define a class to see what gets passed when we get an item:

class Foo(object):
    def __getitem__(self, item):
        return item

>>> f = Foo()
>>> f["key"]
'key'
>>> f[2]
2
>>> f[1:10:3]
slice(1, 10, 3)
>>> f[3:-6]
slice(3, -6, None)
>>> f[:]
slice(None, None, None)

What is this slice object?

Presenter Notes

Attributes

The representaion tells you how to create a slice object:

>>> a = list(range(10))
>>> s = slice(1, 10, 3)
>>> a[s]
[1, 4, 7]

Let's look at the attributes:

>>> s.start
1
>>> s.stop
10
>>> s.step
3
>>> s.step = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: readonly attribute
>>> hash(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'slice'

Slice objects are immutable, but not hashable!

Presenter Notes

Why is the slice not hashable?

Assuming it was hashable, we could write:

>>> d = dict()
>>> s = slice(1, 5)
>>> d[s] = 42           # will actually raise a TypeError
>>> d[1:5]
42

This would be confusing, as a dict is not a sequence!

On the other hand, assuming the slice object would be mutable:

>>> a = "Python"
>>> s = slice(2, 4)
>>> a[s]
'th'
>>> s.start = 0         # will actually raise an AttributeError
>>> a[s]
'Pyth'

Less, confusing. As there is no practical usecase for either, I assume that Guido did not want to leave any room for confusion.

Or as Tim Peters would say:

Special cases aren't special enough to break the rules.
Although practicality beats purity.

Presenter Notes

Example

data = """\
0010George Jetson    1245 Spaceship St    Houston       TX
0020Wile E Coyote    312 Acme Blvd        Tucson        AZ
0030Fred Flintstone  246 Granite Lane     Bedrock       CA
0040Jonny Quest      31416 Science AVE    Palo Alto     CA
0050Anne Costello    326 Michigan Rd      Round Rock    TX
0060Robert Morrison  125 Hyndford St      Grand Island  NE
""".splitlines()

fields = [
    ('id',      slice( 0,  4)),
    ('name',    slice( 4, 21)),
    ('address', slice(21, 42)),
    ('city',    slice(42, 56)),
    ('state',   slice(56, 58)),
]

for record in data:
    for field, sl in fields:
        print("%s: %s" % (field, record[sl]))
    print()

Presenter Notes

Only one method: .indices()

S.indices(len) -> (start, stop, stride)

Assuming a sequence of length len, calculate the start and stop
indices, and the stride length of the extended slice described by
S. Out of bounds indices are clipped in a manner consistent with the
handling of normal slices.

Allows you to easily create your own loops over indices:

class Loopy(object):

    def __init__(self, length):
        self.length = length

    def __getitem__(self, item):
        if isinstance(item, slice):
            for i in range(*item.indices(self.length)):
                yield i
        ...

Presenter Notes

History - Python 1.0.1 (1994)

Python had slicing:

Python 1.0.1
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> s = "Python"
>>> s[1:-2]
'yth'
>>> s[::-1]
  File "<stdin>", line 1
    s[::-1]
       ^
SyntaxError: invalid syntax

But:

  • No slice object

  • Only start and stop argument

  • There was no third step (or stride) argument

Presenter Notes

History - Python 1.4 (1996)

From the release notes:

Changes for use with Numerical Python: builtin function slice() and
Ellipses object, and corresponding syntax:

      x[lo:hi:stride]      ==     x[slice(lo, hi, stride)]
      x[a, ..., z]         ==     x[(a, Ellipses, z)]

Great:

  • slice and Ellipsis objects added by Jim Hugunin and Chris Chase

  • the third slicing step argument was add to the syntax: A[1:10:2], A[:-1:1], A[::-1]

But:

  • Python's built-in sequence types did NOT supported this feature!

Presenter Notes

History - Python 2.3 (2003)

Michael Hudson submitted a patch for Python’s built-in list, tuple, and string sequence types to support the step argument:

>>> s = "Python"
>>> s[::-1]
'nohtyP'

Presenter Notes

Relevance

Normally users of Python don't have to deal with slice objects much, even though they use them all the time.

They become important when writing libraries which support Python's slicing syntax to access arrays. For example: array, bitarray and in particular:

For example 2 dimensional array:

>>> from numpy import array
>>> a = array([[1, 2, 3],
...            [4, 5, 6],
...            [7, 8, 9]])
>>> a[2, 1]
8
>>> a[0, 1:]
array([2, 3])
>>> a[:-1, :-1]
array([[1, 2],
       [4, 5]])

Presenter Notes

Numpy's s_, a nicer way to build up index tuples for arrays:

>>> from numpy import s_
>>> s_[1::-2]
slice(1, None, -2)
>>> s_[1, ::-1, ..., 1:-1:3]
(1, slice(None, None, -1), Ellipsis, slice(1, -1, 3))

We can simplify our earlier example:

fields = [
    ('id',      s_[:4]),
    ('name',    s_[4:21]),
    ('address', s_[21:42]),
    ('city',    s_[42:56]),
    ('state',   s_[56:58]),
]

Presenter Notes

Thank you!

Presenter Notes