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)
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)
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'>)
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__()
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:
If we now initiate an instance of Cockatoo we will find that it will not call the __init__ function of the FlyingBird class
while an initiation of a BeeHummingbird instance does
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__()
>>> Cockatoo()
Cockatoo init
Parrot init
Bird init
>>> BeeHummingbird()
BeeHummingbird init
Hummingbird init
FlyingBird init
Bird init
To understand the order of calls you might want to look at the MRO
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)
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
>>> print(BeeHummingbird.__mro__) (<class 'BeeHummingbird'>, <class 'Hummingbird'>, <class 'FlyingBird'>,
<class 'Bird'>, <type 'object'>)
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)
super().method(args)
cheers
Florian
No comments:
Post a Comment