Multiple Inheritance in Python

True Story Follows

Back in high school and college when I was first learning programming, object oriented programming is always advertised as this amazing thing that will solve world hunger and global warming. But during my tenure as a professional programmer, it’s more likely to find a poor object oriented structure where all you can wonder is who the Knutsons are.

The result for me is to consider object oriented programming a generally poor implementation of code re-use. When I create classes, I rarely inherit or extend those classes. My risk averse approach has left me in a comfort zone that I’ve recently wanted to get past because I feel as though I’ve seen increasingly unnecessarily complex examples of object oriented design. And if I’m going to complain, I have to ask myself, “how would I implement this if I was starting from scratch?”

What Bad Object Oriented Programming Looks Like

It’s far easier to give examples of what bad OOP design looks like because its flaws are so easy to highlight. Moreover, as a Python programmer, a lot of these problems are far more likely because of the dynamic typing system. During my professional tenure, here are some of the examples I’ve found:

  • Mixins are implemented into classes that don’t really create an abstraction. Instead they just mix all your problems together and couple every class together that uses the mixin
  • Deep class hierarchies with seemingly random method overloads lead to a class structure that make it incredibly difficult to understand
  • Sub-classes might exist that you didn’t even know about, and modification to a base class causes a mutation elsewhere that breaks something
  • Poorly designed abstractions break down, and instead of modifying one class, you instead need to modify multiple classes in a chain
  • Mutations in general that would not otherwise happen in pure functions lead to a greater likelihood of bugs
  • The ability to substitute a sub-class for its parent class is not inherently well-maintained by a language like Python; only programmer discipline will gaurantee that all methods of an interface are implemented
  • OOP was used for the wrong purpose entirely, and making changes is cumbersome rather than a quick fix in a single location.

There Has to be a Better Way

As I mentioned, I asked myself the question, “How Would I implement this?” when I’m looking at code I don’t like. My immediate thought was, “Well. I don’t really know.” So I turned to my favorite hero in the world of coding, Bob Martin, and tried to find his thoughts on object oriented design. I found a nice resource at The Principles of Object Oriented Design. Even with its relative brevity, it’s enough to read and think about for days on end.

The very first principle is that of the single responsibility principle, likely the easiest principle to violate, and one that if followed would mitigate violating the other principles. The principle is effectively that a class should have exactly one responsibility. Therefore, the class should have only one reason to mutate. Looking through any code base, this is violated all of the time.

And oddly enough, an example of a way to address this principle is something that I’ve never seen myself in production with Python, so I’m experimenting with an implementation at work (and partly why I finally decided to write about this).

Which finally brings us to…

Multiple Inheritance in Python

So to set the stage, Bob Martin also wrote an interesting article about why interfaces in Java are a disappointment. Java doesn’t support multiple inheritance, and at the same time, interfaces are a big deal, so I guess a lot of boilerplate code gets written. You can check out the article here about harmful interfaces.

If you don’t want to click the link, and if you’re a Python programmer (both of those conditions are probably satisfied if you’re reading my blog), this is basically the example:

class Subject(object):

    def __init__(self):
        self.observers = []
        super(Subject, self).__init__()

    def register(self, observer):

    def notify(self):
        for observer in self.observers:

class ArbitraryWidget(object):

    def __init__(self):
        # 'methalop' is what Ali G suggests as an alternative for the word
        # 'bread' to Noam Chomsky
        self.methalop = 5
        super(ArbitraryWidget, self).__init__()

    def do_something(self):
        self.methalop += 1

class ObservableArbitraryWidget(ArbitraryWidget, Subject):

    def __init__(self):
        super(ObservableArbitraryWidget, self).__init__()

observable_widget = ObservableArbitraryWidget()

Screen Shot 2015-06-04 at 4.38.14 PM

In short, a class ends up getting created that has no implementation details itself. Instead, it’s just a subclass of two different concrete classes, and so now an instance of that class can be treated as either one. To make this more clear, this is how you could treat it if similar code were written in Java where casting is supported (in Python, the type of the class is irrelevant):

ArbitraryWidget arbitraryWidget = ObservableArbitraryWidget();
Subject subject = ObservableArbitraryWidget();

ObservableArbitraryWidget observableArbitraryWidget = ObservableArbitraryWidget();
ArbitraryWidget arbitraryWidget = (ArbitraryWidget)observableArbitraryWidget;
Subject subject = (Subject) observableArbitraryWidget;

Neither of the parent classes know anything about the other, and even though the subclass can be mutated in multiple ways as might conventionally happen in a real code base, each mutation is happening within a separate class on the same instance.

Implementation Details in Python

When I first tried implementing this at work, the first problem was that the constructors of each parent class had to be called, and they happen to have separate parameters. So let’s deal with the first issue.

If we have multiple inheritance where every constructor needs to be called, this will not work:

class CL1(object):

    def __init__(self):
        print "class 1"

class CL2(object):

    def __init__(self):
        print "class 2"

class CL3(CL2, CL1):

instance = CL3()

You can check out the output here:

class 2

The result is that only the first inherited class has its constructor called. You can fix this by adding “super” in every constructor:

class CL1(object):
    def __init__(self):
        super(CL1, self).__init__()
        print "class 1"

class CL2(object):
    def __init__(self):
        super(CL2, self).__init__()
        print "class 2"

class CL3(CL1, CL2):
    def __init__(self):
        super(CL3, self).__init__()
        print "class 3"

instance = CL3()

And now you can see that every constructor gets called:

class 2
class 1
class 3

This in itself isn’t without problems. In my example code, there are no arguments. But as soon as there are, things get a little bit bananas. You could try to gaurantee that every constructor has the exact same arguments, but now you’ve just coupled all the parent classes together and kind of defeated the purpose of multiple inheritance. Alternatively, you could take advantage of python’s ability to pass blanket *args and **kwargs. This works better, but you also need to be sure that everyone on your team is being careful to match arguments and keyword arguments in their function calls. If not, it would be easy to get unexpected behavior.

It’s worth noting that like a lot of powerful tools, this is a case where it would be easy to end up shooting yourself in the foot. As soon as one parent classes takes some action that’s meant to effect the other parent class, the whole thing is polluted. So this is not something to try and attempt for a use case where you’re fitting a square peg into a round hole.

So to quickly illustrate implementation details:

class CL1(object):
    def __init__(self, **kwargs):
        if 'arg1' not in kwargs:
            raise ValueError("Yo.  'arg1' is required even though I didn't specify that in the method header")
        self.something = kwargs.get('arg1')
        super(CL1, self).__init__(**kwargs)

class CL2(object):
    def __init__(self, **kwargs):
        if 'arg2' not in kwargs:
            raise ValueError("Yo.  'arg2' is required even though I didn't specify that in the method header")
        self.something_else = kwargs.get('arg2')
        super(CL2, self).__init__()

class CL3(CL1, CL2):
    def __init__(self, **kwargs):
        super(CL3, self).__init__(**kwargs)

junk = CL3(arg1=1, arg2=2)

On another “gotcha” note, note that for CL2, I had to NOT use **kwargs for the super() call. Otherwise I get a TypeError (WHAT? That’s crazy). So to clean things up a bit more and make things less confusing, maybe inherit from something different from object.

Use Cases

As far as where this might be useful, someone on StackOverflow made a really good example. The point to highlight is how each parent class is something distinctly separate from the other parent classes. Anyway, one use case might be where you have some sort of game, and in this game there are a bunch of weaponized vehicles. So you might create abstract classes for WeaponType, VehicleType, and DisplayType. Then you have implementations of those abstract classes. So you might have a MachineGunWeapon and a LaserWeapon and a GrenateWeapon. Then for VehicleType you might have TrackedVehicle and HelicopterVehicle and WheeledVehicle. Then for DisplayType you might have an GoodGuyDisplayType and a BadGuyDisplayType. Now it would be trivial to create permutations of those types with multiple inheritance and add new types as necessary and with ease.

For my particular use case at work, I’m essentially mapping all of the logic associated with a 3rd party business provider to a single class. All of the parent classes are a distinct business operation, but it’s all consolidated on one object. Then I have multiple classes for each 3rd party business partner that we’re using. By doing this, I can avoid logical checks all over the place saying “for this business operation, if it’s this business partner, instantiate this class…” Instead, I can just say one time that for a particular business partner, instantiate one class.

In Conclusion

I could be totally wrong and over time the code just rots and this ends up being another disaster. But we’ll see.

The End

  • ffc

    Very nice writeup, thanks!