Search code examples
pythonlistsubclass

Is there a simple way to override the list object's method __getitem__?


I am trying to define a default style list object:

class ilist(list):
    def __init__(self,r=list(),dft=None):
        list.__init__(self,r)
        self.dft=dft
    def __getitem__(self,n):
        if len(self)<=n:
            for i in range(n-len(self)+1):
                self.append(self.dft)
        for i,v in enumerate(self):
            if i+1==len(self):
                return v

x=ilist()
print x[4]
print x

It works.

>>> 
None
[None, None, None, None, None]  

But I think it's terrible to query my ilist. I've tried the following method:

def __getitem__(self,n):
    from operator import getitem
    if len(self)<=n:
        for i in range(n-len(self)+1):
            self.append(self.dft)
    return getitem(self,n)

but the fact shows it totally equals self[n] and causes RuntimeError: maximum recursion depth exceeded

I also tried to borrow the parent class list method .But the form isx.__getitem__(y). I don't know how to adapt it to ilist.

So finally my terrible solution comes out. Raw and brute force..Is there any effecient or simple solution? Thanks in advance.


Solution

  • Use super() to access the original __getitem__:

    def __getitem__(self,n):
        while len(self) <= n:
            self.append(self.dft)
        return super(ilist, self).__getitem__(n)
    

    Demo:

    >>> class ilist(list):
    ...     def __init__(self,r=list(),dft=None):
    ...         list.__init__(self,r)
    ...         self.dft=dft
    ...     def __getitem__(self, n):
    ...         while len(self) <= n:
    ...             self.append(self.dft)
    ...         return super(ilist, self).__getitem__(n)
    ... 
    >>> il = ilist()
    >>> il[3]
    >>> il
    [None, None, None, None]
    >>> il[2] = 5
    >>> il
    [None, None, 5, None]
    >>> il[2]
    5
    

    You probably want to support slicing as well:

    def __getitem__(self, n):
        maxindex = n
        if isinstance(maxindex, slice):
            maxindex = maxindex.indices(len(self))[1]
        while len(self) <= maxindex:
            self.append(self.dft)
        return super(ilist, self).__getitem__(n)
    

    and if you wanted to support assignment to arbitrary indices as well, add a __setitem__ method:

    def __setitem__(self, n, val):
        maxindex = n
        if isinstance(maxindex, slice):
            maxindex = maxindex.indices(len(self))[1]
        while len(self) <= maxindex:
            self.append(self.dft)
        return super(ilist, self).__setitem__(n, val)
    

    but then you could move the default-value creation out to a helper method:

    class ilist(list):
        def __init__(self, r=None, dft=None):
            if r is None:
                r = []
            list.__init__(self, r)
            self.dft=dft
    
        def _ensure_length(n):
            maxindex = n
            if isinstance(maxindex, slice):
                maxindex = maxindex.indices(len(self))[1]
            while len(self) <= maxindex:
                self.append(self.dft)
    
        def __getitem__(self, n):
            self._ensure_length(n)
            return super(ilist, self).__getitem__(n)
    
        def __setitem__(self, n, val):
            self._ensure_length(n)
            return super(ilist, self).__getitem__(n)