Python: Is it possible to have a class method that acts on a slice of the class?
I am creating a container class in Python that either inherits from list
or just implements all the standard list methods (I don't care what).
How do I create a method that will only act on the items returned from the slice? I was able to create a method that will act on the entire container (see below), but I cannot figure out how to act on the slice only.
I am using python 2.7.6 and am using it from __future__ import print_function, division
in all my code.
Sample code:
from __future__ import print_function, division
import itertools
class MyContainerClass(list):
""" For now, I'm inheriting from list. Is this my problem? """
def __init__(self, data_array):
list.__init__(self, data_array)
def __getitem__(self, *args):
arg = args[0]
# specific indices MyContainerClass[0, 3, 5] => indexes 0, 3, and 5
if isinstance(arg, (list, tuple)) and not isinstance(arg[0], bool):
return [list.__getitem__(self, _i) for _i in arg]
# standard slice notation
elif isinstance(arg, slice):
return list.__getitem__(self, arg)
# Using an array mask MyContainerClass[[True, False, False,...,True]] => index 0 and -1]
elif isinstance(arg[0], bool): # or isinstance(arg, np.ndarray):
return list(itertools.compress(self, arg))
else:
# I'll eventually replace this with and exception raise.
return 'error'
def my_method(self):
"""
Will act on entire list, but I want it to act on only what's
returned by the slice (which may be the entire list in some cases).
"""
return "a, ".join([str(_i) for _i in self])
Here's an example of the use I would like:
>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> data[5:]
[6, 7]
>>> data.my_method() # This works as expected
"1a, 2a, 3a, 4a, 5a, 6a, 7"
>>> data[0:3].my_method() # Doesn't work
"1a, 2a, 3" # but should return this
It looks like everything is working now. Thanks guys! This is what I came up with:
from __future__ import print_function, division
import itertools
class MyContainerClass(list):
"""
"""
def __init__(self, array):
if isinstance(array, int):
list.__init__(self, [array])
else:
list.__init__(self, array)
def __getitem__(self, arg):
# Standard Slice notation
if isinstance(arg, slice):
retval = super(MyContainerClass, self).__getitem__(arg)
# specific indices
elif isinstance(arg, (list, tuple)) and not isinstance(arg[0], bool):
retval = [list.__getitem__(self, _i) for _i in arg]
# a single specific index
elif isinstance(arg, int):
retval = list.__getitem__(self, arg)
# an array mask of T/F values
elif isinstance(arg[0], bool): # or isinstance(arg, np.ndarray):
retval = list(itertools.compress(self, arg))
# raise an error on unknown
else:
raise SyntaxError("Unknown notation for list slice or index")
retval = type(self)(retval)
return retval
def __getslice__(self, i, j):
# Python 2 built-in types only
return self.__getitem__(slice(i, j))
def my_method(self):
return "a, ".join([str(_i) for _i in self])
And acts like:
>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> mask = [True, True, False, True, False, False, False]
>>> print(data)
[1, 2, 3, 4, 5, 6, 7]
>>> print(type(data[5:]))
<class '__main__.MyContainerClass'>
>>> print(data.my_method())
1a, 2a, 3a, 4a, 5a, 6a, 7
>>> print(data[0:5].my_method())
1a, 2a, 3a, 4a, 5
>>> print(data[1, 5, 2].my_method())
2a, 6a, 3
>>> print(data[mask].my_method())
1a, 2a, 4
>>> print(data[2].my_method())
3
source to share
You need to make sure yours __getitem__
returns your type again:
class MyContainerClass(list):
""" For now, I'm inheriting from list. Is this my problem? """
def __init__(self, data_array):
list.__init__(self, data_array)
def my_method(self):
"""
Will act on entire list, but I want it to act on only what's
returned by the slice (which may be the entire list in some cases).
"""
return "a, ".join([str(_i) for _i in self])
def __getitem__(self, index):
retval = super(MyContainerClass, self).__getitem__(index)
if isinstance(index, slice):
retval = type(self)(retval)
return retval
def __getslice__(self, i, j):
# Python 2 built-in types only
return self.__getitem__(slice(i, j))
The extra __getslice__
method is only required in Python 2, and then only if you inherit from a type that already implements __getslice__
. list
- that type.
Demo:
>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> data[:5]
[1, 2, 3, 4, 5]
>>> type(data[:5])
<class '__main__.MyContainerClass'>
>>> data.my_method()
'1a, 2a, 3a, 4a, 5a, 6a, 7'
>>> data[:3].my_method()
'1a, 2a, 3'
source to share