Implementing slicing in __getitem__

Question:

I am trying to implement slice functionality for a class I am making that creates a vector representation.

I have this code so far, which I believe will properly implement the slice but whenever I do something like v[4] where v is a vector, python raises an error about not having enough arguments. So I am trying to figure out how to define the __getitem__ special method in my class to handle both plain indexes and slicing.

def __getitem__(self, start, stop, step):
    index = start
    if stop == None:
        end = start + 1
    else:
        end = stop
    if step == None:
        stride = 1
    else:
        stride = step
    return self.__data[index:end:stride]
Asked By: nicotine

||

Answers:

The __getitem__() method will receive a slice object when the object is sliced. Simply look at the start, stop, and step members of the slice object in order to get the components for the slice.

>>> class C(object):
...   def __getitem__(self, val):
...     print val
... 
>>> c = C()
>>> c[3]
3
>>> c[3:4]
slice(3, 4, None)
>>> c[3:4:-2]
slice(3, 4, -2)
>>> c[():1j:'a']
slice((), 1j, 'a')

The correct way to do this is to have __getitem__ take one parameter, which can either be a number or a slice object.

Answered By: carl

I have a "synthetic" list (one where the data is larger than you would want to create in memory) and my __getitem__ looks like this:

def __getitem__(self, key):
    if isinstance(key, slice):
        # Get the start, stop, and step from the slice
        return [self[ii] for ii in xrange(*key.indices(len(self)))]
    elif isinstance(key, int):
        if key < 0: # Handle negative indices
            key += len(self)
        if key < 0 or key >= len(self):
            raise IndexError, "The index (%d) is out of range." % key
        return self.getData(key) # Get the data from elsewhere
    else:
        raise TypeError, "Invalid argument type."

The slice doesn’t return the same type, which is a no-no, but it works for me.

Answered By: Walter Nissen

How to define the getitem class to handle both plain indexes and slicing?

Slice objects gets automatically created when you use a colon in the subscript notation – and that is what is passed to __getitem__. Use isinstance to check if you have a slice object:

from __future__ import print_function

class Sliceable(object):
    def __getitem__(self, subscript):
        if isinstance(subscript, slice):
            # do your handling for a slice object:
            print(subscript.start, subscript.stop, subscript.step)
        else:
            # Do your handling for a plain index
            print(subscript)

Say we were using a range object, but we want slices to return lists instead of new range objects (as it does):

>>> range(1,100, 4)[::-1]
range(97, -3, -4)

We can’t subclass range because of internal limitations, but we can delegate to it:

class Range:
    """like builtin range, but when sliced gives a list"""
    __slots__ = "_range"
    def __init__(self, *args):
        self._range = range(*args) # takes no keyword arguments.
    def __getattr__(self, name):
        return getattr(self._range, name)
    def __getitem__(self, subscript):
        result = self._range.__getitem__(subscript)
        if isinstance(subscript, slice):
            return list(result)
        else:
            return result

r = Range(100)

We don’t have a perfectly replaceable Range object, but it’s fairly close:

>>> r[1:3]
[1, 2]
>>> r[1]
1
>>> 2 in r
True
>>> r.count(3)
1

To better understand the slice notation, here’s example usage of Sliceable:

>>> sliceme = Sliceable()
>>> sliceme[1]
1
>>> sliceme[2]
2
>>> sliceme[:]
None None None
>>> sliceme[1:]
1 None None
>>> sliceme[1:2]
1 2 None
>>> sliceme[1:2:3]
1 2 3
>>> sliceme[:2:3]
None 2 3
>>> sliceme[::3]
None None 3
>>> sliceme[::]
None None None
>>> sliceme[:]
None None None

Python 2, be aware:

In Python 2, there’s a deprecated method that you may need to override when subclassing some builtin types.

From the datamodel documentation:

object.__getslice__(self, i, j)

Deprecated since version 2.0: Support slice objects as parameters to the __getitem__() method. (However, built-in types in CPython currently still implement __getslice__(). Therefore, you have to override it in derived classes when implementing slicing.)

This is gone in Python 3.

To extend Aaron’s answer, for things like numpy, you can do multi-dimensional slicing by checking to see if given is a tuple:

class Sliceable(object):
    def __getitem__(self, given):
        if isinstance(given, slice):
            # do your handling for a slice object:
            print("slice", given.start, given.stop, given.step)
        elif isinstance(given, tuple):
            print("multidim", given)
        else:
            # Do your handling for a plain index
            print("plain", given)

sliceme = Sliceable()
sliceme[1]
sliceme[::]
sliceme[1:, ::2]

“`

Output:

('plain', 1)
('slice', None, None, None)
('multidim', (slice(1, None, None), slice(None, None, 2)))
Answered By: Eric Cousineau
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.