In this blog post, I will explain how the
super() function in Python works. The super function is a
built-in Python function and can be used within a class to gain access to inherited methods from a parent class that has been overwritten.
So let's look at an example. Assume we want to build a dictionary class which has all properties of
dict, but additionally allows us to write to logger. This can be done by defining a class which inherits from the
dict class and overwrites the functions with new functions which do the same as the old, but additionally have the logging call. The new
dict class would look like this
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info("Setting key %s to %s" % (key, value))
super().__setitem__(key, value)
def __getitem__(self, key):
logging.info("Access key %s" % key)
super().__getitem__(key)
Here we overwrite the
__getitem__ and
__setitem__ methods with new ones, but we just want to add a logging functionality and keep the functionality of the original functions. Note that we do not need
super() to do this since we could get the same result with
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info("Setting key %s to %s" % (key, value))
dict.__setitem__(self, key, value)
def __getitem__(self, key):
logging.info("Access key %s" % key)
dict.__getitem__(self, key)
The advantage of
super() is that, should you decide that you want to inherit from a different class, you would only need to change the first line of the class definition, while the explicit use of the parent class requires you to go through the entire class and change the parent class name everywhere, which can become quite cumbersome for large classes. So
super() makes your code more maintainable.
However, the functionality of
super() gets more complicated if you inherit from multiple classes and if the function you refer to is present in more than one of these parent classes. Since the parent class is not explicitly declared, which parent class is addressed by
super()?
The
super() function considers an order of the inherited classes and goes through that ordered list until it finds the first match. The ordered list is known as the Method Resolution Order (MRO). You can see the MRO of any class as
>>> dict.__mro__
(<type 'dict'>, <type 'object'>)
The use of the MRO in the
super() function can lead to very different results in the case of multiple inheritances, compared to the explicit declaration of the parent class. Let's go through another example where we define a
Bird class which represents the parent class for the
Parrot class and the
Hummingbird class:
class Bird(object):
def __init__(self):
print("Bird init")
class Parrot(Bird):
def __init__(self):
print("Parrot init")
Bird.__init__(self)
class Hummingbird(Bird):
def __init__(self):
print("Hummingbird init")
super(Hummingbird, self).__init__()
Here we used the explicit declaration of the parent class in the
Parrot class, while in the
Hummingbird class we use
super(). From this, I will now construct an example where the
Parrot and
Hummingbird classes will behave differently because of the
super() function.
Let's create a
FlyingBird class which handles all properties of flying birds. Non-flying birds like ostriches would not inherit from this class:
class FlyingBird(Bird):
def __init__(self):
print("FlyingBird init")
super(FlyingBird, self).__init__()
Now we produce child classes of
Parrot and
Hummingbird, which specify specific types of these animals. Remember,
Hummingbird uses super,
Parrot does not:
class Cockatoo(Parrot, FlyingBird):
def __init__(self):
print("Cockatoo init")
super(Cockatoo, self).__init__()
class BeeHummingbird(Hummingbird, FlyingBird):
def __init__(self):
print("BeeHummingbird init")
super(BeeHummingbird, self).__init__()
If we now initiate an instance of
Cockatoo we will find that it will not call the
__init__ function of the
FlyingBird class
>>> Cockatoo()
Cockatoo init
Parrot init
Bird init
while an initiation of a
BeeHummingbird instance does
>>> BeeHummingbird()
BeeHummingbird init
Hummingbird init
FlyingBird init
Bird init
To understand the order of calls you might want to look at the MRO
>>> print(BeeHummingbird.__mro__)
(<class 'BeeHummingbird'>, <class 'Hummingbird'>, <class 'FlyingBird'>,
<class 'Bird'>, <type 'object'>)
This is an example where not using
super() is causing a bug in the class initiation since all our
Cockatoo instances will miss the initiation functionality of the
FlyingBird class. It clearly demonstrates that the use of
super() goes beyond just avoiding explicit declarations of a parent class within another class.
Just as a side note before we finish, the syntax for the
super() function has changed between Python 2 and Python 3. While the Python 2 version requires an explicit declaration of the arguments, as used in this post, Python 3 now does all this implicitly, which changes the syntax from (Python 2)
super(class, self).method(args)
to
I hope that was useful. Let me know if you have any comments/questions. Note that there are very useful discussions of this topic on
stack-overflow and in this
blog post.
cheers
Florian