Python inheriting UserList and exteding with new attributes

Viewed 42

I want to extend the UserList class (https://docs.python.org/3/library/collections.html#collections.UserList) with new attributes like so:

class GroceryList(UserList):
  def __init__(self, initlist, max_item_weight, max_item_volume):
    super().__init__(initlist)
    self.max_item_weight = max_item_weight
    self.max_item_volume = max_item_volume

Now if I slice GroceryList, I lose max_item_weight and max_item_volume attributes so I override the __getitem__ method:

def __getitem__(self, i):
    if isinstance(i, slice):
        return self.__class__(self.data[i], self.max_item_weight, self.max_item_volume)
    else:
        return self.data[i]

How can I override the UserList __getitem__ method so that it is more generic and I don't have to override the __getitem__ method for every new type of list I create, e.g. GroceryList, ToolsList, NonEdibleList... because I want to keep the attributes even after slicing my derived lists.

1 Answers

You can make a copy of the object and change only the self.data variable. Something like the following:

from copy import deepcopy

class UserList2(UserList):
  def __getitem__(self, i):
      if isinstance(i, slice):
          obj = deepcopy(self)
          obj.data = self.data[i]
          return obj
      else:
          return self.data[i]

Then you can inherit from this class in the same manner as you would from UserList. for e.g.,

class GroceryList(UserList2):
  def __init__(self, initlist, max_item_weight, max_item_volume):
    super().__init__(initlist)
    self.max_item_weight = max_item_weight
    self.max_item_volume = max_item_volume

l1 = GroceryList([1,2,3,4,5,6,7,8,9], 7, 6)
print(l1[3:7], l1[3:7].max_item_weight, l1[3:7].max_item_volume)

gives [4, 5, 6, 7] 7 6 as output where list is the sliced data and 7,6 are the original attributes.


Another approach is as following but this requires some extra bookeeping and using keyword arguments when inheriting.

class UserList2(UserList):
  def __init__(self, initlist, **kwargs):
    super().__init__(initlist)
    self.constants = kwargs.keys()
    for k, v in kwargs.items():
        self.__setattr__(k, v)

  def __getitem__(self, i):
      if isinstance(i, slice):
          return self.__class__(self.data[i], **{k: self.__getattribute__(k) for k in self.constants})
      else:
          return self.data[i]

Same example then changes to:

class GroceryList(UserList2):
  def __init__(self, initlist, max_item_weight, max_item_volume):
    super().__init__(initlist, max_item_weight=max_item_weight, max_item_volume=max_item_volume)

l1 = GroceryList([1,2,3,4,5,6,7,8,9], 7, 6)
print(l1[3:7], l1[3:7].max_item_weight, l1[3:7].max_item_volume)

which gives the same output.

Related