Chapter 14. Multiple Inheritance

Multiple inheritance is where one class inherits from two or more other classes. Although Python (and, for example, C++) fully supports multiple inheritance, some languages—most notably, Java—don’t allow it. One problem is that multiple inheritance can lead to the same class being inherited more than once (e.g., if two of the base classes inherit from the same class), and this means that the version of a method that is called, if it is not in the subclass but is in two or more of the base classes (or their base classes, etc.), depends on the method resolution order, which potentially makes classes that use multiple inheritance somewhat fragile.

Multiple inheritance can generally be avoided by using single inheritance (one base class), and setting a metaclass if we want to support an additional API, since as we will see in the next subsection, a metaclass can be used to give the promise of an API without actually inheriting any methods or data attributes. An alternative is to use multiple inheritance with one concrete class and one or more abstract base classes for additional APIs. And another alternative is to use single inheritance and aggregate instances of other classes.

Nonetheless, in some cases, multiple inheritance can provide a very convenient solution. For example, suppose we want to create a new version of the Stack class from the previous subsection, but want the class to support loading and saving using a pickle. We might well want to add the loading and saving functionality to several classes, so we will implement it in a class of its own:

class LoadSave:

    def __init__(self, filename, *attribute_names):
        self.filename = filename
        self.__attribute_names = []
        for name in attribute_names:
            if name.startswith("__"):
                name = "_" + self.__class__.__name__ + name
            self.__attribute_names.append(name)

   def save(self):
       with open(self.filename, "wb") as fh:
           data = []
           for name in self.__attribute_names:
               data.append(getattr(self, name))
           pickle.dump(data, fh, pickle.HIGHEST_PROTOCOL)

   def load(self):
       with open(self.filename, "rb") as fh:
           data = pickle.load(fh)
           for name, value in zip(self.__attribute_names, data):
               setattr(self, name, value)


The class has two attributes: filename, which is public and can be changed at any time, and __attribute_names, which is fixed and can be set only when the instance is created. The save() method iterates over all the attribute names and creates a list called data that holds the value of each attribute to be saved; it then saves the data into a pickle. The with statement ensures that the file is closed if it was successfully opened, and any file or pickle exceptions are passed up to the caller. The load() method iterates over the attribute names and the corresponding data items that have been loaded and sets each attribute to its loaded value.

Here is the start of the FileStack class that multiply-inherits the Undo class from the previous subsection and this subsection’s LoadSave class:

class FileStack(Undo, LoadSave):

    def __init__(self, filename):
        Undo.__init__(self)
        LoadSave.__init__(self, filename, "__stack")
        self.__stack = []

    def load(self):
        super().load()
        self.clear()


The rest of the class is just the same as the Stack class, so we have not reproduced it here. Instead of using super() in the __init__() method we must specify the base classes that we initialize since super() cannot guess our intentions. For the LoadSave initialization we pass the filename to use and also the names of the attributes we want saved; in this case just one, the private __stack. (We don’t want to save the __undos; and nor could we in this case since it is a list of methods and is therefore unpicklable.)

The FileStack class has all the Undo methods, and also the LoadSave class’s save() and load() methods. We have not reimplemented save() since it works fine, but for load() we must clear the undo stack after loading. This is necessary because we might do a save, then do various changes, and then a load. The load wipes out what went before, so any undos no longer make sense. The original Undo class did not have a clear() method, so we had to add one:

       def clear(self):          # In class Undo
           self.__undos = []


In the Stack.load() method we have used super() to call LoadSave.load() because there is no Undo.load() method to cause ambiguity. If both base classes had had a load() method, the one that would get called would depend on Python’s method resolution order. We prefer to use super() only when there is no ambiguity, and to use the appropriate base name otherwise, so we never rely on the method resolution order. For the self.clear() call, again there is no ambiguity since only the Undo class has a clear() method, and we don’t need to use super() since (unlike load()) FileStack does not have a clear() method.

What would happen if, later on, a clear() method was added to the FileStack class? It would break the load() method. One solution would be to call super().clear() inside load() instead of plain self.clear(). This would result in the first super-class’s clear() method that was found being used. To protect against such problems we could make it a policy to use hard-coded base classes when using multiple inheritance (in this example, calling Undo.clear(self)). Or we could avoid multiple inheritance altogether and use aggregation, for example, inheriting the Undo class and creating a LoadSave class designed for aggregation.

What multiple inheritance has given us here is a mixture of two rather different classes, without the need to implement any of the undo or the loading and saving ourselves, relying instead on the functionality provided by the base classes. This can be very convenient and works especially well when the inherited classes have no overlapping APIs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset