Although I used Python a long time and OOP, I never really dwelled into the reasons that someone would use
super instead of other ways in Python. Usually I would use other ways in an effort do avoid confusing words and those ugly underscores. Sometimes however it is worth making something a bit less readable and such a case is super.
Why should you learn to use super though? For a single reason.. super equals less headaches.
Calling a parent class’ initializer
Remember that with initializer I merely mean the
__init__ method of a class. So let’s take for example the class A below.
class A(object): def __init__(self): print('This is A') def hello(self): print('Hello, this is a method from A')
When we instantiate this class, the initializer runs and thus we get printed ‘This is A’. Now we want B to inherit A:
class B(A): def __init__(self): print('This is B')
The result is a hybrid class – half A, half B. The problem is that both classes have an initializer and in such cases the hybrid’s methods, variables, etc. are preferred. So in practice B has the method
hello that it inherited from A but will only run its own initializer.
In real life we tend to run the initializer for every parent class. This is simply because of how program designs tend to be. In our simple example a way to solve this is to explicitly call the initializer of A:
class B(A): def __init__(self): A.__init__(self) print('This is B')
The self always makes me dizzy so I will explain a bit on it. Namely why can’t we just have
A.__init__()? The reason is that A is not an instance but a class.
self however is an instance and that’s why we use it. As you might have noticed though, it is an instance of B and still we pass it to A as if it was an instance of A. So why the hell does it work?
The reason it works is that as we said B is a hybrid – half A, half B. This is very similar to having a double citizenship. A half Greek, half Norwegian can be recognized in both Greece and Norway. In the same way A and B can be recognized as either A or B. Logical, aye?
The bless of not knowing
The above example works fine. But what if one changes the name of A into G? For a simple example like ours, we could just change every occurence of A into G. However if you are dealing with medium to large projects you might have many classes that inherit from A and way many files. Furthermore if you have tests, you probably have all sort of test classes that inherit as well.
The point is that in such cases a little change somewhere can invoke havoc. The programmer will need to keep track of every single place where we inherit class A which just is not practical. That’s where super comes into play.
super we can call A’s initializer without ever typing the name of the class:
class B(A): def __init__(self): super(B, self).__init__() # notice we type B, not A print('This is B')
Now, no matter if you rename A to G or V, you won’t have to make any changes to classes that inherit from that class!
The bless of caring even less
So you saw how
super takes away the problem of having to keep track of class names we inherit from. I think all this makes much more sense when we inherit from multiple classes.
Say we have classes X and Y:
class X(object): def __init__(self): print('This is X') class Y(object): def __init__(self): print('This is Y')
Now if B inherit from everyone else, in the no-super way it will look like this:
class B(A, X, Y): def __init__(self): A.__init__(self) X.__init__(self) Y.__init__(self) print('This is B')
With super we can minimize it to:
class B(A): def __init__(self): super(B, self).__init__(self) print('This is B')
At first glance this looks like we merely minimize the code to a single line. The real benefit however is that if we did not use super, now our class B would be much more prone to mistakes since either A, X, or Y might change name somewhere (more classes – higher probability of a rename).
I hope all this makes it very apparent that in big OOP projects where you have a lot of interaction between objects, classes, etc. Using super is just a simple trick that adds a huge gain for the programmer. So whenever you need to call an initializer (or any other method) from a parent class, please save yourself some trouble and use
Python 2 issues
You might have noticed that I use
object in every parent class in the examples above. In Python 3 you don’t have to do this.
class A(object): ..
This merely makes a class inherit from object. The problem in Python 2 you see is that not everything is an object. As such we have to explicitly state it. In Python 3 all classes inherit from object be default so the code becomes much cleaner. Notice that even super is much cleaner in Python 3:
class A: .. class B(A): def __init__(self): super().__init__() # no self pollution ..