If you’re coming from Part 1, you probably think I’m a pretty big fan of Python. Well here is where I start to set the record straight.
As I mentioned in the previous post, I believe that all good object oriented programming languages should have proper support for inheritance. But, what exactly is proper with regards to inheritance? Haven’t we all been taught to favor composition over inheritance? So, why do we need inheritance anyway?
Composing behaviors instead of inheriting them is a great way to build loosely coupled code. It also enables really thorough testing through the injection of mocks or stubs into the class under test. But lets be real… just because we favor composition doesn’t mean it is a replacement for inheritance.
Inheritance is great when we want to enhance some behavior of a class. For example, imagine that we have some really complicated class like a UI widget which contains all sorts of logic for rendering itself. Lets say that there are dozens of methods which handle positioning, color, etc. Now, we could use composition here to create a new widget class that basically just passes through every method call to the inner widget, but that means tons of boilerplate just to change a tiny bit of functionality. And really, are we any more loosely coupled? Our widget just passes everything through. If we choose to inherit the widget and just extend the class, we can override the single method we want to modify the behavior of and be done with it.
So now lets focus on inheritance in Python. To inherit behavior from a base class in Python, you use the following syntax:
class Person: def __init__(self, name): self.name = name class Employee(Person): def __init__(self, name, salary): Person.__init__(name) self.salary = salary
This is a pretty contrived example, but an Employee is a person. Pretty straightforward. Now, because Python is “dynamically typed”, if there is a change to the Person class here that makes changes to the constructor, we will not catch the error until runtime. Phooey. I guess its fair to say that this isn’t so much a Python issue as it is one that is common to all languages without compile-time checking. Ruby has the same problem for example. Sure, it lets you write code quicker, but its broken code.
I know, I know… that would never happen to you. Of course you would know that your employee is a Person and you know that if you change the Person constructor, you have to update the Employee constructor too. But what if your Employee is like this:
class Humanoid(Bipedal): pass class Person(Humanoid): pass class Payable: pass class Fireable: pass class Reimbursable: pass class Employee(Person, Payable, Fireable, Reimbursable): pass
Yes, thats right folks… Python allows for multiple inheritance. Yay? First of all, I recognize that this too is a really contrived example but stay with me here for a second. Lets say that in fact you are not in control of the classes you have decided to inherit from but rather they are imports of modules published by other teams. Now in order to be confident in upgrading the version of any module, you have to know exactly which modules contain exactly which classes and exactly where you are using them. That might be easy if you are the only contributor to your codebase, but thats simply not feasible for a large team or a large codebase. If you’re lucky and you have good test coverage, you might catch these problems before they get to production. If not, you could end up fighting a 2 AM fire because it seemed like a good idea to use multiple inheritance.
So, Python supports multiple inheritance but be careful how and when you use it. In the decades I have been writing code, I have never had a case where I needed to use multiple inheritance. In my opinion, its sloppy and enables poor design.
So, strike one against you Python. I guess its up to how well you encapsulate your data to settle the score.