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')
How to implement slice in Python 3?
You need to use the slice.indices
method. Given the length of your sequence, it returns a tuple of start, stop, step:
>>> s = slice(2, 5, None)
>>> s.indices(10)
(2, 5, 1)
>>> [x for x in range(*s.indices(10))]
[2, 3, 4]
>>> s.indices(3)
(2, 3, 1)
>>> s.indices(0)
(0, 0, 1)
Implementing __getitem__
You could use exception handling; assume key
is a slice
object and call the indices()
method on it. If that fails it must've been an integer:
def __getitem__(self, key):
try:
return [self.somelist[i] * 5 for i in key.indices(self.length)]
except AttributeError:
# not a slice object (no `indices` attribute)
return self.somelist[key] * 5
Most use-cases for custom containers don't need to support slicing, and historically, the __getitem__
method only ever had to handle integers (for sequences, that is); the __getslice__()
method was there to handle slicing instead. When __getslice__
was deprecated, for backwards compatibility and for simpler APIs it was easier to have __getitem__
handle both integers and slice
objects.
And that is ignoring the fact that outside sequences, key
doesn't have to be an integer. Custom classes are free to support any key type they like.
How to implement the lazy slicing of a class instance?
Instead of returning a generator, return a view. This is an object that conceptually represents the sequence of elements in question. We can do this by storing a reference to the original A instance, plus a range that encodes the instances. When we index into the view, we can ask the range which original index (or indices) are involved.
Supposing we set up the general structure:
class A(list):
def __init__(self, n):
super().__init__()
self[:] = [i for i in range(0, n)]
def _at(self, idx):
# custom logic here to return the correct value for self[idx]
def __getitem__(self, idx):
if isinstance(idx, int):
return self._at(idx)
elif isinstance(idx, slice):
# The `indices` method of a slice object converts to
# start, stop, step values which we can use to construct a range.
return A_view(self, range(*idx.indices(len(self))))
else:
raise TypeError # this is not an appropriate use for `assert`
Then our view could look like:
class A_view:
def __init__(self, original, indices):
self._original, self._indices = original, indices
def __getitem__(self, idx):
if isinstance(idx, int):
return self._original[self._indices[idx]]
elif isinstance(idx, slice):
return A_view(self._original, self._indices[idx])
else:
raise TypeError
def __len__(self):
return len(self._indices)
The idea is that if we receive an integer index, we translate it through the range
object into an index for the original A
, and call back to its __getitem__
(with an integer this time). If we receive another slice
, we use it to slice our range into a sub-range, and make another view.
Note that your A class should already be iterable, due to inheriting from list
. To make the view iterable (and also get the in
operator, forward and reverse iteration, .index
and .count
methods for free), you can inherit from collections.abc.Sequence
. You just need a __getitem__
and __len__
- both easy to implement, as above - and the base class will do the rest (while also advertising to isinstance
that your class has these properties).
How do __getitem__, __setitem__, work with slices?
The problem is that you're subclassing a builtin, and so have to deal with a few wrinkles. Before I delve into that issue, I'll go straight to the "modern" way:
How will things work in the future? Is there a "list" class - that I am not aware of - that I could extend and would not present this inconvenience?
Yes, there's the stdlib Abstract Base Classes. You can avoid the ugly complications caused by subclassing builtin list
by using the ABCs instead. For something list-like, try subclassing MutableSequence
:
from collections import MutableSequence
class MyList(MutableSequence):
...
Now you should only need to deal with __getitem__
and friends for slicing behaviour.
If you want to push ahead with subclassing the builtin list
, read on...
Your guess is correct, you will need to override __getslice__
and __setslice__
. The language reference explains why and you already saw that:
However, built-in types in CPython currently still implement
__getslice__()
. Therefore, you have to override it in derived classes when implementing slicing.
Note that l[3:7]
will hook into __getslice__
, whereas the otherwise equivalent l[3:7:]
will hook into __getitem__
, so you have to handle the possibility of receiving slices in both methods... groan!
How to implement a secondary custom method for object slicing, other than __getitem__ in Python
The [
]
syntax requires a proper object with a __getitem__
method. In order to have a "slice method", use a property
that returns a helper which supports slicing.
The helper simply holds a reference to the actual parent
object, and defines a __getitem__
with the desired behaviour:
class VectorIloc:
def __init__(self, parent):
self.parent = parent
# custom logic for desired "iloc" behaviour
def __getitem__(self, item):
key = list(self.parent.dictionary)[item]
return self.parent[key]
On the actual class, merely define the desired "method" as a property
that returns the helper or as an attribute:
class Vector:
def __init__(self, map_object: dict):
self.dictionary = map_object
# if .iloc is used often
# self.iloc = VectorIloc(self)
def __getitem__(self, key):
return self.dictionary[key]
# if .iloc is used rarely
@property
def iloc(self):
return VectorIloc(self)
Whether to use a property
or an attribute is an optimisation that trades memory for performance: an attribute constructs and stores the helper always, while a property
constructs it only on-demand but on each access. A functools.cached_property
can be used as a middle-ground, creating the attribute on first access.
The property
is advantageous when the helper is used rarely per object, and especially if it often is not used at all.
Now, when calling vector.iloc[3]
, the vector.iloc
part provides the helper and the [3]
part invoces the helper's __getitem__
.
>>> vector = Vector({0:0, 1: 1, 2: 2, "three": 3})
>>> vector.iloc[3]
3
Python slice objects and __getitem__
The Python grammar defines when you can use the slice operator:
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
subscript: test | [test] ':' [test] [sliceop]
sliceop: ':' [test]
test
is pretty much any expression, but it is only inside a subscriptlist
that you can use the slice operator. So yes, the square brackets when used for subscripting are what matter, but square brackets used for lists won't magically allow you to write a slice, nor can you put a slice inside an arbitrary expression that just happens to be inside a subscript.
If you want slices when you aren't subscripting something you'll have to write slice(a,b,c)
.
How does list slicing hook into __getitem__?
The language reference says it all. Specifically:
Deprecated since version 2.0 : Support slice objects as parameters
__getitem__()
method. (However, built-in types in CPython currently still implement__getslice__()
. Therefore, you have to override it in derived classes when implementing slicing.)
and:
Called to implement evaluation of self[i:j] ...
Note it doesn't handle self[i:j:]
...
Related Topics
How to Append One String to Another in Python
Python: JSON.Loads Returns Items Prefixing with 'U'
Replace() Method Not Working on Pandas Dataframe
How to Decrypt Openssl Aes-Encrypted Files in Python
"Cannot Open Include File: 'Config-Win.H': No Such File or Directory" While Installing MySQL-Python
Making the Background Move Sideways in Pygame
JSONify a SQLalchemy Result Set in Flask
Django 1.7 Throws Django.Core.Exceptions.Appregistrynotready: Models Aren't Loaded Yet
How to Set the Value of a Pandas Column as List
How to Convert a Date String to Different Format
How to Run Python Code from Sublime Text 2
Numpy Array Assignment with Copy
How to Pass a Method as a Parameter in Python
Python CSV Error: Line Contains Null Byte
Is There Any Other Way to Load a Resource Like an Image, Sound, or Font into Pygame
Python3: Importerror: No Module Named '_Ctypes' When Using Value from Module Multiprocessing