6

I'm trying to build a class that inherits methods from Python's list, but also does some additional things on top... it's probably easier just to show code at this point...

class Host(object):
    """Emulate a virtual host attached to a physical interface"""
    def __init__(self):
    # Insert class properties here...
    pass

class HostList(list):
    """A container for managing lists of hosts"""
    def __init__(self):
        self = []

    def append(self, hostobj): 
        """append to the list...""" 
        if hostobj.__class__.__name__ == 'Host': 
            self.insert(len(self), hostobj)
        else:
            _classname = hostobj.__class__.__name__
            raise RuntimeError, "Cannot append a '%s' object to a HostList" % _classname

My problem is this... if I want to perform the same kind of object admission tests on insert() as I did on append(), I can't find a way to code the new methods without to sacrificing support for one list expansion method (i.e. list.append(), list.insert(), or list.extend()). If I try to support them all, I wind up with recursive loops. What is the best way around this problem?

Edit... please find my final answer, based on Nick's recommendation below...

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
  • 4
    `self = []` does not do what you think it does. – Ignacio Vazquez-Abrams Apr 07 '11 at 19:35
  • 1
    `super(HostList, self).__init__(self)` would do the trick. What your code does is reassign the (argument) variable `self` to `[]`. – Fred Foo Apr 07 '11 at 19:42
  • `__getitem__` will return a list object if a slice is specified. You could change the initializer to `__init__(self, l = None)` that will use a list if provided. Then in `__getitem__`, if ii is a slice object, then return `HostList(self._list[ii])` – Jeremy Brown Apr 08 '11 at 15:56
  • Also, you don't necessarily have to define the `append()` method, as `MutableSequence` already does that for you, provided you have defined the `insert()` method (check http://hg.python.org/cpython/file/3.4/Lib/_collections_abc.py#l711 for more info). – linkyndy Aug 24 '14 at 09:17

5 Answers5

7

If you can possibly avoid it, don't inherit from builtin classes. (You can, but that doesn't mean you should without a really compelling reason)

Those classes are optimised for speed, and that makes inheriting from them correctly quite tedious, since you end up having to override almost everything.

Inheriting from collections.MutableSequence instead lets you implement just a few essential methods, and get a robust fully featured implementation of the sequence API, without all the quirks and caveats that come with inheriting from list.

ncoghlan
  • 40,168
  • 10
  • 71
  • 80
5

Use isinstance to check your objects to see if they're instances of Host, and use super (e.g. super(HostList, self).insert(...)) to use the functionality of list, rather than reimplementing it yourself.

You should end up with something like:

def append(self, obj): 
    """append to the list..."""
    if not isinstance(obj, Host):
        raise RuntimeError, "Cannot append a '%s' object to a HostList" % obj.__class__.__name__
    super(HostList, self).append(obj)
bradley.ayers
  • 37,165
  • 14
  • 93
  • 99
  • 1
    Even better, define a static method `as_host(x)` that returns `x` if it's a host, else throws the appropriate exception. Then the `super` calls become one-liners. – Fred Foo Apr 07 '11 at 19:40
2

Unless if there's a compelling reason to have your HostList container completely support the mutable container interface, I suggest using a has-a model rather than is-a. You'll have the extra burden of ensuring type consistency with operations such as slicing (return a HostList container rather than a list).

Jeremy Brown
  • 17,880
  • 4
  • 35
  • 28
0

You could call list's method from your method, with super(). That way you don't need to kludge it with the other methods.

Sami J. Lehtinen
  • 1,039
  • 6
  • 9
0

I used collections.MutableSequence instead of subclassing Python's list()**

The resulting code... posting here in case, it helps someone...

from collections.abc import MutableSequence

class MyList(MutableSequence):
    """
    An extensive user-defined wrapper around list objects.

    Inspiration:
        https://github.com/python/cpython/blob/208a7e957b812ad3b3733791845447677a704f3e/Lib/collections/__init__.py#L1174https://github.com/python/cpython/blob/208a7e957b812ad3b3733791845447677a704f3e/Lib/collections/__init__.py#L1174
    """

    def __init__(self, initlist=None):
        self.data = []
        if initlist is not None:
            if isinstance(initlist, list):
                self.data[:] = initlist

            elif isinstance(initlist, MyList):
                self.data[:] = initlist.data[:]

            else:
                self.data = list(initlist)

    def __repr__(self):
        return """<{} data: {}>""".format(self.__class__.__name__, repr(self.data))

    def __lt__(self, other):
        return self.data < self.__cast(other)

    def __le__(self, other):
        return self.data <= self.__cast(other)

    def __eq__(self, other):
        return self.data == self.__cast(other)

    def __gt__(self, other):
        return self.data > self.__cast(other)

    def __ge__(self, other):
        return self.data >= self.__cast(other)

    def __cast(self, other):
        return other.data if isinstance(other, MyList) else other

    def __contains__(self, value):
        return value in self.data

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

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

    def __setitem__(self, idx, value):
        # optional: self._acl_check(val)
        self.data[idx] = value

    def __delitem__(self, idx):
        del self.data[idx]

    def __add__(self, other):
        if isinstance(other, MyList):
            return self.__class__(self.data + other.data)

        elif isinstance(other, type(self.data)):
            return self.__class__(self.data + other)

        return self.__class__(self.data + list(other))

    def __radd__(self, other):
        if isinstance(other, MyList):
            return self.__class__(other.data + self.data)

        elif isinstance(other, type(self.data)):
            return self.__class__(other + self.data)

        return self.__class__(list(other) + self.data)

    def __iadd__(self, other):
        if isinstance(other, MyList):
            self.data += other.data

        elif isinstance(other, type(self.data)):
            self.data += other

        else:
            self.data += list(other)

        return self

    def __mul__(self, nn):
        return self.__class__(self.data * nn)

    __rmul__ = __mul__

    def __imul__(self, nn):
        self.data *= nn
        return self

    def __copy__(self):
        inst = self.__class__.__new__(self.__class__)
        inst.__dict__.update(self.__dict__)

        # Create a copy and avoid triggering descriptors
        inst.__dict__["data"] = self.__dict__["data"][:]

        return inst

    def append(self, value):
        self.data.append(value)

    def insert(self, idx, value):
        self.data.insert(idx, value)

    def pop(self, idx=-1):
        return self.data.pop(idx)

    def remove(self, value):
        self.data.remove(value)

    def clear(self):
        self.data.clear()

    def copy(self):
        return self.__class__(self)

    def count(self, value):
        return self.data.count(value)

    def index(self, idx, *args):
        return self.data.index(idx, *args)

    def reverse(self):
        self.data.reverse()

    def sort(self, /, *args, **kwds):
        self.data.sort(*args, **kwds)

    def extend(self, other):
        if isinstance(other, MyList):
            self.data.extend(other.data)

        else:
            self.data.extend(other)
Mike Pennington
  • 41,899
  • 19
  • 136
  • 174