Implementing slicing in __getitem__ Implementing slicing in __getitem__ python python

Implementing slicing in __getitem__


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


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.


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_functionclass 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 resultr = 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 rTrue>>> 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.