147

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] 

5 Answers 5

158

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') 
Sign up to request clarification or add additional context in comments.

6 Comments

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__
@gregorySalvan: Doesn't that compatibility example below that section just recurse?
@Eric: No, because the presence of the second colon bypasses __get/set/delslice__. It's pretty subtle, though.
@user2357112: Wow, completely missed that second colon - thanks!
@alancalvitti IIRC, that's for creating new-style classes in Python 2.
|
92

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.

2 Comments

Shouldn't if key >= len( self ) be if key < 0 or key >= len( self ) ? What if a key < -len(self) is passed?
Instead of "Invalid argument type" I suggest a more helpfulf"{type(self).__name__} indices must be integers or slices, not {type(key).__name__}" (inspired by what you get if you try a["hello"] when a is a simple python list)
42

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.

2 Comments

when we handle plain indices, we cannot call self[index] as it'll go into recursion, how do you access the right element?
If you want to use a parent's implementation of a method you are already in, use super(). see stackoverflow.com/questions/222877/…
9

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

1 Comment

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.
9

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.