75

I am interested in using the python list object, but with slightly altered functionality. In particular, I would like the list to be 1-indexed instead of 0-indexed. E.g.:

>> mylist = MyList()
>> mylist.extend([1,2,3,4,5])
>> print mylist[1]

output should be: 1

But when I changed the __getitem__() and __setitem__() methods to do this, I was getting a RuntimeError: maximum recursion depth exceeded error. I tinkered around with these methods a lot but this is basically what I had in there:

class MyList(list):
    def __getitem__(self, key):
        return self[key-1]
    def __setitem__(self, key, item):
        self[key-1] = item

I guess the problem is that self[key-1] is itself calling the same method it's defining. If so, how do I make it use the list() method instead of the MyList() method? I tried using super[key-1] instead of self[key-1] but that resulted in the complaint TypeError: 'type' object is unsubscriptable

Any ideas? Also if you could point me at a good tutorial for this that'd be great!

Thanks!

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
mindthief
  • 12,755
  • 14
  • 57
  • 61
  • 22
    This violates the Liskov Substitution Principle pretty blatantly. There might not be a lot of value in subclassing `list` if you can't actually use it anywhere that a `list` would be expected. Perhaps composition would be a more appropriate strategy in this case? – Ken Nov 04 '10 at 01:08
  • Don't quite understand; what do you mean by "composition"? Also, how can we be sure that we can't substitute MyList in place of a standard list? Is there a good way to tell? For example, if internal methods use `__getitem__` and `__setitem__` then would there be a problem? – mindthief Nov 04 '10 at 02:03
  • 7
    mindthief: http://en.wikipedia.org/wiki/Composition_over_inheritance -- and if you change the existing interface of `list` (i.e., exactly what you're asking for here), then you can't substitute a `MyList` for a `list`. Even basic things like `x[0]` won't work. – Ken Nov 08 '10 at 23:28

4 Answers4

75

Use the super() function to call the method of the base class, or invoke the method directly:

class MyList(list):
    def __getitem__(self, key):
        return list.__getitem__(self, key-1)

or

class MyList(list):
    def __getitem__(self, key):
        return super(MyList, self).__getitem__(key-1)

However, this will not change the behavior of other list methods. For example, index remains unchanged, which can lead to unexpected results:

numbers = MyList()
numbers.append("one")
numbers.append("two")

print numbers.index('one')
>>> 1

print numbers[numbers.index('one')]
>>> 'two'
Jordan
  • 476
  • 7
  • 16
Gintautas Miliauskas
  • 7,744
  • 4
  • 32
  • 34
  • 4
    Also, beware of other list methods breaking because you're changing a behaviour of indexing which is a fundamental part of list objects. – Gintautas Miliauskas Nov 04 '10 at 00:58
  • 1
    I'll +1 if you note why the recursion occurs. You give the solution without explaining the problem. – Nathan Ernst Nov 04 '10 at 01:01
  • 9
    Why the recursion occurs was explained perfectly well in the question. – Peter Milley Nov 04 '10 at 01:05
  • 3
    One other gotcha to point out here: you'd think super(MyList, self)[key-1] would work, but it doesn't. super() explicitly doesn't work with any "implicit lookups" like [] instead of __getitem__. – Peter Milley Nov 04 '10 at 01:10
  • "beware of other list methods breaking" shouldn't those methods use `__getitem__` and `__setitem__` internally? – mindthief Nov 04 '10 at 01:35
  • @mindthief: if they were using `__getitem__`, they could be accessing it as `self.__getitem__` which would invoke your implementation. – Gintautas Miliauskas Nov 29 '10 at 22:29
34

Instead, subclass integer using the same method to define all numbers to be minus one from what you set them to. Voila.

Sorry, I had to. It's like the joke about Microsoft defining dark as the standard.

Binary Phile
  • 2,538
  • 16
  • 16
  • 10
    What's the joke about Microsoft defining dark as the standard? **Edit**: nvm, [found it](https://www.ocf.berkeley.edu/~mbarrien/jokes/lightblb.txt): *"Q: How many Microsoft hardware engineers does it take to change a light bulb? A: None, they redifine darkness as an industry standard"* – wjandrea Jul 23 '20 at 18:37
27

You can avoid violating the Liskov Substitution principle by creating a class that inherits from collections.MutableSequence, which is an abstract class. It would look something like this:

def indexing_decorator(func):
    def decorated(self, index, *args):
        if index == 0:
            raise IndexError('Indices start from 1')
        elif index > 0:
            index -= 1
        return func(self, index, *args)
    return decorated


class MyList(collections.MutableSequence):
    def __init__(self):
        self._inner_list = list()

    def __len__(self):
        return len(self._inner_list)

    @indexing_decorator
    def __delitem__(self, index):
        self._inner_list.__delitem__(index)

    @indexing_decorator
    def insert(self, index, value):
        self._inner_list.insert(index, value)

    @indexing_decorator
    def __setitem__(self, index, value):
        self._inner_list.__setitem__(index, value)

    @indexing_decorator
    def __getitem__(self, index):
        return self._inner_list.__getitem__(index)

    def append(self, value):
        self.insert(len(self) + 1, value)
Neuron
  • 5,141
  • 5
  • 38
  • 59
Aleksandar Jovanovic
  • 1,127
  • 1
  • 11
  • 14
  • `x = MyList(); x.append(4); y = MyList(); print(y)` Try it out. You get interesting results... – zondo May 24 '16 at 22:53
  • 1
    Almost there. You should not adjust the index for `insert`. Also, try to handle case where index == 0. – Hai Vu May 24 '16 at 23:11
  • Yes, you are right. But then the indexing would not be consistent. I edited the code in the answer a bit. It should work well now, I think. – Aleksandar Jovanovic May 25 '16 at 10:50
  • Would it violate the Liskov substitution principle if one inherits from collections.UserList instead? e.g. class MyList(collections.UserList): – warren.sentient May 11 '19 at 16:26
  • If you have something like `for x in thelist:` and the list contains 3 items, they you will get a call to `__getitem__(self, index)` where index is 4 and a list_index out of range error. Perhaps someone can verify this and provide a fix? – tpc1095 Aug 23 '23 at 15:45
-8
class ListExt(list):
    def extendX(self, l):
        if l:
            self.extend(l)
Floern
  • 33,559
  • 24
  • 104
  • 119