pythoninheritancecompositionobject-composition

What would be the pythonic way to implement a class that represents "a list with a title"?


I am trying to better understand composition vs inheritance in python so I have crafted a hopefully simple example.

Suppose I want to implement a class TitledList that is intended to behave just like a standard list, but with an additional attribute title intended to hold a string containing the name of the list. The __repr__ and __str__ methods would likely have to be reimplemented to incorporate the title, but otherwise I'd like to retain all the native functionality of list. Initializers would accept an optional keyword-only argument title.

I can see two approaches to creating this class. I could go the composition route, and define a class with two attributes, .contents (a regular list) and .title. That would presumably look something along the lines of

class TitledList:
    def __init__(self, contents=[], *, title=None):
        self.contents = list(contents)
        self.title = title

But I'm not sure how I would go about cribbing all the methods of list so I don't have to constantly be referring to my_titled_list.contents all over the place or reimplement all the list methods I use.

The other alternative is to do the thing that everyone on the whole Internet says not to do, and inherit from the list class. I think in that case I would do the initializer like this?

class TitledList(list):
    def __init__(self, iterable=[], *, title=None):
        super().__init__(iterable)
        self.title = title

This seems a lot more straightforward to me. But surely everyone on the whole Internet says not to extend list for a reason.

What are the pros and cons here? Is there a simple way to make the composition solution work the way I intuitively want? Are there lots of drawbacks to the inheritance solution that I don't understand? Is there some third option I should be considering (UserList?)


Solution

  • There are several ways to do it.

    1. Subclass collections.UserList instead of list. This is basically a wrapper around list designed to be extensible.

    2. Subclass collections.abc.MutableSequence and implement all of the required abstract methods. This might be useful if you want to define a totally new sequence-like class.

    3. Subclass list. This might actually be fine in limited scenarios like your TitledList example, where you're just adding a new attribute. But if you want to ovverride list's existing methods then this may not be a good idea.

    4. Create a new object with a list attribute. This is simple and easy to understand, but might be inconvenient. It all depends on your use case.

    Reference: Trey Hunner: The problem with inheriting from dict and list in Python