Implementing slicing in __getitem__
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__
nicotine
Updated on March 11, 2020Comments
-
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 thegetitem
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 almost 10 yearsNote: 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 about 8 yearsShouldn't if key >= len( self ) be if key < 0 or key >= len( self ) ? What if a key < -len(self) is passed?
-
Eric almost 8 years@gregorySalvan: Doesn't that compatibility example below that section just recurse?
-
user2357112 about 7 years@Eric: No, because the presence of the second colon bypasses
__get/set/delslice__
. It's pretty subtle, though. -
Eric about 7 years@user2357112: Wow, completely missed that second colon - thanks!
-
Eric Cousineau about 7 yearsAs 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 about 5 years@alancalvitti IIRC, that's for creating new-style classes in Python 2.
-
Luca over 4 yearsNote that
__getitem__
can receive aslice
, but could also receive anint
as inc[0]
. see docs.python.org/3/reference/datamodel.html#object.__getitem__ -
Jitin over 2 yearswhen 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 over 2 yearsIf you want to use a parent's implementation of a method you are already in, use
super()
. see stackoverflow.com/questions/222877/…