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.