ListMixin (WAS: How do you control _all_ items added to a list?)

Michael Spencer mahs at telcopartners.com
Tue Mar 1 21:21:39 CET 2005


Steven Bethard wrote:
> Nick Coghlan wrote:
>  > Hmm, it might be nice if there was a UserList.ListMixin that was the
>  > counterpart to UserDict.DictMixin
> 
> I've thought this occasionally too.  One of the tricky issues though is 
> that often you'd like to define __getitem__ for single items and have 
> ListMixin add the code for slices.  I haven't figured out how to do this 
> cleanly yet...
> 
> STeVe
I agree that would be useful.  One solution would be to ask users to implement 
__getsingleitem__ (and not __getitem__) if they want the mixin to handle slice 
logic.  The following illustrates that, and also falls back to slicing the 
iterator if it is provided:

class ProtoListMixin(object):
     """Prototype ListMixin, exploring slice interface and semantics"""
     def __getitem__(self, index):
         if isinstance(index, slice):
             start, stop, step = index.start or 0, index.stop, index.step or 1
             if start < 0 or stop < 0 or not stop:
                 try:
                     start, stop, step = index.indices(len(self))
                 except TypeError:
                     raise TypeError, "unsized object"

             try:
                 getter = self.__getsingleitem__
                 return [getter(i) for i in range(start, stop, step)]
             except AttributeError:
                 pass
         else:
             if index < 0:
                 try:
                     index = len(self) + index
                 except TypeError:
                     raise TypeError, "unsized object"
             try:
                 return self.__getsingleitem__(index)
             except AttributeError:
                 pass
             start, stop, step = index, index + 1, None

         # Alternatively, try to use the iterator, if available
         import itertools
         try:
             args = [iter(self)]
         except AttributeError:
             raise TypeError, "Must implement __getsingleitem__ or __iter__"

         if start:
             args.append(start)
         args.append(stop)
         if step:
             if step < 1:
                 raise ValueError, "slicing an iterable requires step >=1"
             args.append(step)

         iterator = itertools.islice(*args)
         if isinstance(index, slice):
             return list(iterator)
         else:
             try:
                 return iterator.next()
             except StopIteration:
                 raise IndexError, "index out of range"


# Users should implement __getsingleitem__ for positive indices

class Index(ProtoListMixin):
     def __init__(self, data):
         """For testing, provide a list"""
         self._data = data
     def __getsingleitem__(self, index):
         return self._data[index]

# If __len__ is implemented, negative indices are supported

class IndexLen(Index):
     def __len__(self):
         return len(self._data)

# If __getsingleitem__ is not implemented, positive slices are returned
# from an iterator

class Iter(ProtoListMixin):
     def __init__(self, data):
         """For testing, provide an iterable"""
         self._data = data
     def __iter__(self):
         return iter(self._data)


  >>> a = Index(range(10))
  >>> a[4]
  4
  >>> a[4:8]
  [4, 5, 6, 7]
  >>> a[-4]
  Traceback (most recent call last):
    File "<input>", line 1, in ?
    File "ListMixin", line 22, in __getitem__
  TypeError: unsized object

  >>> b = IndexLen(range(10))
  >>> b[-4]
  6

  >>> c = Iter(xrange(10))
  >>> c[3]
  3
  >>> c[3:6]
  [3, 4, 5]
  >>> c[-3]
  Traceback (most recent call last):
    File "<input>", line 1, in ?
    File "ListMixin", line 22, in __getitem__
  TypeError: unsized object
  >>>




More information about the Python-list mailing list