27

I make a class like below:

class MyList(list):
    def __init__(self, lst):
        self.list = lst

I want slice functionality to be overridden in MyList

attaboyabhipro
  • 1,450
  • 1
  • 17
  • 33

3 Answers3

53

You need to provide custom __getitem__(), __setitem__ and __delitem__ hooks.

These are passed a slice object when slicing the list; these have start, stop and step attributes. However, these values could be None, to indicate defaults. Take into account that the defaults actually change when you use a negative stride!

However, they also have a slice.indices() method, which when given a length produces a tuple of (start, stop, step) values suitable for a range() object. This method takes care of such pesky details as slicing with a negative strides and no start or stop indices:

def __getitem__(self, key):
    if isinstance(key, slice):
        indices = range(*key.indices(len(self.list)))
        return [self.list[i] for i in indices]
    return self.list[key]

or, for your case:

def __getitem__(self, key):
    return self.list[key]

because a list can take the slice object directly.

In Python 2, list.__getslice__ is called for slices without a stride (so only start and stop indices) if implemented, and the built-in list type implements it so you'd have to override that too; a simple delegation to your __getitem__ method should do fine:

def __getslice__(self, i, j):
    return self.__getitem__(slice(i, j))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    **Brilliant answer.** It should be noted, however, that this is... *incomplete.* The `key` passed to `__getitem__()` could also be a `tuple` containing one or more `slice` items, which would need to be handled similarly. The `__setitem__()` and `__delitem__()` dunder methods also need similar generalization, of course. See [this `MultiIndexingList` example](https://riptutorial.com/python/example/1571/indexing-custom-classes----getitem------setitem---and---delitem--) for relevant subclass logic handling all possible edge cases. – Cecil Curry Jul 09 '20 at 06:30
  • 1
    @CecilCurry I specifically focus on how *Python lists* are sliced. Extended slicing is not handled by the standard list type, which is why I left that out. – Martijn Pieters Jul 09 '20 at 07:07
8
class MyList(list):
    def __init__(self, lst):
        self.list = lst

doesn't make much sense... self is the list object itself, and it has already been created at this point, maybe you want to override __new__, however you probably don't need to touch that. Anyway you want to override __getitem__ like so:

def __getitem__(self, val):
    if isinstance( val, slice):
        # do stuff here
jamylak
  • 128,818
  • 30
  • 231
  • 230
0

As both @Martijn Pieters and @jamylak said, you need to implement the __getitem__ method.

In general, if one wants to override the slicing functionality of it's super class, and keep using super's methods that already uses super's __getitem__, just split it into cases, and benefit from both worlds.

For example:

import pandas as pd

class SubFrame(pd.DataFrame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __getitem__(self, item):
        if isinstance(item, slice):
            # your implementation
            print("'My slicing'")
            return SubFrame(super().__getitem__(item))
        else:
            return super().__getitem__(item)

Now you can do:

>>> sf = SubFrame({'A': (10,20,30,40,50)})
>>> sf[1:-2]
'My slicing'
    A
1  20
2  30
>>> type(sf[1:-2])
<class '__main__.SubFrame'>

And [] will still call super's __getitem__

>>> sf['A']  # super's __getitem__
0    10
1    20
2    30
3    40
4    50
Name: A, dtype: int64
>>> type(sf['A'])
<class 'pandas.core.series.Series'>
Yair
  • 58
  • 6