Implementing slicing in __getitem__

84,255

Solution 1

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

Solution 2

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.

Solution 3

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.

Solution 4

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

Solution 5

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

See:

http://docs.python.org/library/functions.html#slice

http://docs.python.org/reference/datamodel.html#object.__getitem__

Share:
84,255
nicotine
Author by

nicotine

Updated on March 11, 2020

Comments

  • nicotine
    nicotine over 4 years

    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 a call like v[4] where v is a vector python returns an error about not having enough parameters. 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]
    
  • gregorySalvan
    gregorySalvan almost 10 years
    Note: for extending builtins types like list or tuple you must implements __getslice__ for python 2.X versions. see docs.python.org/2/reference/datamodel.html#object.__getslice‌​__
  • estan
    estan about 8 years
    Shouldn't if key >= len( self ) be if key < 0 or key >= len( self ) ? What if a key < -len(self) is passed?
  • Eric
    Eric almost 8 years
    @gregorySalvan: Doesn't that compatibility example below that section just recurse?
  • user2357112
    user2357112 about 7 years
    @Eric: No, because the presence of the second colon bypasses __get/set/delslice__. It's pretty subtle, though.
  • Eric
    Eric about 7 years
    @user2357112: Wow, completely missed that second colon - thanks!
  • Eric Cousineau
    Eric Cousineau about 7 years
    As a minor follow-up, here is an example of employing this to map between MATLAB indexing and NumPy indexing (which is presently not supported in MATLAB R2016b), with an example usage of it.
  • wjandrea
    wjandrea about 5 years
    @alancalvitti IIRC, that's for creating new-style classes in Python 2.
  • Luca
    Luca over 4 years
    Note that __getitem__ can receive a slice, but could also receive an int as in c[0]. see docs.python.org/3/reference/datamodel.html#object.__getitem_‌​_
  • Jitin
    Jitin over 2 years
    when we handle plain indices, we cannot call self[index] as it'll go into recursion, how do you access the right element?
  • Russia Must Remove Putin
    Russia Must Remove Putin over 2 years
    If you want to use a parent's implementation of a method you are already in, use super(). see stackoverflow.com/questions/222877/…