77

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!

3
  • 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? Commented Nov 4, 2010 at 1: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? Commented Nov 4, 2010 at 2:03
  • 7
    mindthief: 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. Commented Nov 8, 2010 at 23:28

4 Answers 4

76

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'
Sign up to request clarification or add additional context in comments.

6 Comments

Also, beware of other list methods breaking because you're changing a behaviour of indexing which is a fundamental part of list objects.
I'll +1 if you note why the recursion occurs. You give the solution without explaining the problem.
Why the recursion occurs was explained perfectly well in the question.
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.
"beware of other list methods breaking" shouldn't those methods use __getitem__ and __setitem__ internally?
|
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.

1 Comment

What's the joke about Microsoft defining dark as the standard? Edit: nvm, found it: "Q: How many Microsoft hardware engineers does it take to change a light bulb? A: None, they redifine darkness as an industry standard"
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)

5 Comments

x = MyList(); x.append(4); y = MyList(); print(y) Try it out. You get interesting results...
Almost there. You should not adjust the index for insert. Also, try to handle case where index == 0.
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.
Would it violate the Liskov substitution principle if one inherits from collections.UserList instead? e.g. class MyList(collections.UserList):
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?
-8
class ListExt(list):
    def extendX(self, l):
        if l:
            self.extend(l)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.