making list index possible for python class list

Viewed 266

Python has no function for list index argument in __getitem()__, __setitem()__, __delitem()__. Since I have been an R user for long time, it looks quite natural to utilize list index. I know there is pandas or numpy but it is cumbersome to change the type. AND IT NEEDS IMPORTING EXTRA PACKAGE!

>>> import numpy as np
>>> lst = [1,2,3,4,5]
>>> np.array(lst)[[0,3,4]]
array([1, 4, 5])

Here is my proposal.

class list2(__builtins__.list):
    def __getitem__(self, x):
        if isinstance(x, __builtins__.list):
            #print(x)
            return [__builtins__.list.__getitem__(self, y) for y in x]
        else:
            return __builtins__.list.__getitem__(self,x)
    def __setitem__(self, index, elem):
        if isinstance(index, __builtins__.list):
            if isinstance(elem, __builtins__.list):
                for i,x in zip(index, elem):
                    __builtins__.list.__setitem__(self, i, x)
            else:
                for i in index:
                    __builtins__.list.__setitem__(self, i, elem)
                #self[i] = x
        else:
            __builtins__.list.__setitem__(self, index, elem)
    def __delitem__(self, index):
        if isinstance(index, __builtins__.list):
            for i in sorted(index, reverse = True):
                __builtins__.list.__delitem__(self, i)
                #self[i] = x
        else:
            __builtins__.list.__delitem__(self, index)

It seems to work okay.

>>> l = list2(['a', 'b', 'c', 'd', 'e'])
>>> l[[2,3]]
['c', 'd']
>>> l[[2,3]]= ['-', '-']
>>> l
['a', 'b', '-', '-', 'e']
>>> del l[[0,3]]
>>> l
['b', '-', 'e']

But is there any pitfall? or is there any part that needs to be improved?

1 Answers

The main pitfall with something like this is that it's 'astonishing', which is generally frowned upon in Python - we expect the default list to behave like the default list.

Aside from that, your implementation mostly looks complete to me. There are maybe a couple of issues:

  • What happens if we use __setitem__ or __delitem__ and list the same index more than once?
  • What happens if we give __setitem__ lists with mismatched lengths?

There's one key thing that I think it would be worth changing: get rid of lists of indexes, and use tuples instead.

If you pass a comma separated list of indices to __getitem__, these are actually parsed as a tuple!

>>> class Thing:
...     def __getitem__(self, indices):
...         print(indices)
...
>>> t = Thing()
>>> t[1, 2, 3]
(1, 2, 3)

So, rather than using lists, expect tuples and ditch the double brackets.

In terms of other improvements, it's probably better to get rid of the references to __builtins__, and use super() to access the functions from the default list implementation, and it's a good idea to add a custom __repr__ so we don't mix this up with the default list.

If you add all of these suggestions, it would look something like this (typing added for clarity):

from typing import Any, Tuple, Union

class list2(list):
    def __getitem__(self, index: Union[int, Tuple[int], slice]) -> Any:
        if isinstance(index, (slice, int)):
            return super().__getitem__(index)

        return [super(list2, self).__getitem__(x) for x in index]

    def __setitem__(self, index: Union[int, Tuple[int], slice], elem: Any):
        if isinstance(index, (slice, int)):
            return super().__setitem__(index, elem)

        if len(index) != len(elem):
            raise ValueError("Number of elements in index does not match element.")

        for x, element in zip(index, elem):
            self[x] = element

    def __delitem__(self, index: Union[int, Tuple[int], slice]):
        if isinstance(index, (slice, int)):
            return super().__delitem__(index)

        for x in sorted(set(index), reverse=True):
            super().__delitem__(x)

    def __repr__(self) -> str:
        return f"list2({super().__repr__()})"

Related