Super Inherit Your Python Class

Image for post
Image for post
Photo by Carlos de Almeida on Unsplash

Not long ago, I started to use Python in my projects at the spare time. Luckily, as it shares quite a lot of similarities with Ruby, it’s not difficult to get onboard. But there’re some interesting topics in Python that worth digging deeper, such as what we discuss today, inheritance using super keyword.

Normally we write a class with inheritance like below.

class Demo():
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

In this way, we can easily create class inheritance if needed. But how so? And what does the keyword do?

Let’s start with a simple example:

class Quadrilateral:
def __init__(self, length, height):
self.length = length
self.height = height
def area(self):
return self.length * self.height
class Square(Quadrilateral):
def __init__(self, length):
super().__init__(length, length)

Here inside class method, we can use to call the of the class, which pass the params to the class method. So is the class get inherited as well through the keyword.

You can also call super without the step like the example below, but it’s an anti-pattern.

class Cuboid(Quadrilateral):
def volume(self):
face_area = super().area()
return face_area * self.length
>>> cuboid = Cuboid(2,3)
>>> cuboid.volume()
12

The reason we can do this is because inherits from and doesn’t really do anything differently for than it already does for since the parameter is the same (length, height(width)). The of the superclass () will be called automatically.

So what is doing?

The returns a proxy object, a substitute object that can call methods of the base class via delegation. This is called indirection (ability to reference base object with )

Since the indirection is computed at the runtime, we can use different base classes at different times .

Most of the time, we don’t need to pass in any parameter in the , it will default to the current base class. But we can if we want.

It takes 2 parameters. The first is the class whose parent's scope we're trying to resolve to, and the second argument is the object of interest to indicate which object to apply the scope to.

Consider a class hierarchy , , and where each class is the parent of the one following it, and , , and respective instances of each.

super(B, b) 
# resolves to the scope of B's parent i.e. A
# and applies that scope to b, as if b was an instance of A
super(C, c)
# resolves to the scope of C's parent i.e. B
# and applies that scope to c
super(B, c)
# resolves to the scope of B's parent i.e. A
# and applies that scope to c

Under the hood returns a bound method to bound to the object, which gives the method the object’s context such as any instance attributes.(c has all instance attributes of A in the last example).

In the example we use earlier, we can change the call to:

class Square(Quadrilateral):
def __init__(self, length):
super(Square, self).__init__(length, length)

In Python 3, the call is equivalent to the parameterless call, this is telling to lookup the method is parent class, aka: .

Now let’s look at a more complex example, multiple inheritance.

class Quadrilateral:
def __init__(self, length, height):
self.length = length
self.height = height
def area(self):
return self.length * self.height
class Rectangle(Quadrilateral):
def __init__(self, length, height):
super().__init__(length, height)
class Circle:
def __init__(self, radius):
self.radius = radius

def area(self):
return π * radius* radius

class Cylinder(Circle, Square):
def __init__(self, radius, height):
self.radius = radius
self.height = height

def area(self):
base_area1 = super().area() * 2
base_length = π * radius * 2
base_area2 = super.area()
return base_area2 + base_area1

The problem, though, is that both superclasses ( and ) has a method. So when we call the highlighted method above, which method of the parent class we are calling from?

Luckily, we have a method resolution order (MRO). Method Resolution Order (MRO) is the order in which methods should be inherited in the presence of multiple inheritance. You can view the MRO by using the attribute.

>>> Cylinder.__mro__
(<class '__main__.Cylinder'>, <class '__main__.Circle'>,
<class '__main__.Square'>, <class '__main__.Quadrilateral'>,
<class 'object'>)

So it’s clear that when the in gets found, Python will call it instantly. Because only takes one param (radius), Python throws an .

What we can do is to make sure the signatures of the method unique both by making sure the method names or method parameters unique. So we can simply rename the to be .

But we still have the problem with the multiple inheritance, as the inheritance chain grow longer, how can we make sure all the super methods get called have a matching method and method arguments?

There are several issues to be solved along the way according to super considered super:

  • the caller and callee need to have a matching argument signature
  • the method being called by super() needs to exist
  • and every occurrence of the method needs to use super()

How can we make sure the first requirement is met?

We can use the python unpacking methods to get store all the arguments get passed in, getting all the keyword arguments stripped off for each level as required, and forwarding the rest to the next level class. When it reaches the end of the chain, , there’d be no arguments left in the dictionary.

class Sneaky:
def __init__(self, sneaky = false, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sneaky = sneaky
class Person:
def __init__(self, human = false, *args, **kwargs):
super().__init__(*args, **kwargs)
self.human = human
class Thief(Sneaky, Person):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
t = Thief(human = true, sneaky = true)
print(t.human)
# True

So we can see the in the first class, will get stripped off, in , will get stripped off, in , nothing will be in the **.

If you want to make 100% safe just in case there are some params get passed into object. Then add a class to absorb the rest of the params before it reaches the final . Make sure it inserted in the end of the MRO.

class Base(object):
def __init__(self, *args, **kwargs): pass

Now the second question is, how can we make sure the method being called by super() exist?

Similar to the method above, we can implement a Base class to have this methodA if you worry about a subclass incorporates a class that has a methodA() method, so that this subclass won’t call on without reaching class.

class Base:
def methodA(self):
# the delegation chain stops here
assert not hasattr(super(), 'methodA')
class Thief(Base):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def methodA(self):
super().methodA()

Noted that this is very similar to implement the inheritance without using super keyword:

class ChildB(Base):
def __init__(self):
mro = type(self).mro()
for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
if hasattr(next_class, '__init__'):
next_class.__init__(self)
break

The last problem is to make sure that super() is called on all the methods along the chain. (normally we will call it on ).

Why is this necessary? So use the same example as above:

class Sneaky:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Person:
def __init__(self, human=True, *args, **kwargs):
super().__init__(*args, **kwargs)
self.human = human
class Thief(Sneaky, Person):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
t = Thief()
print(t.human)
# True

If the call were removed from , then would raise , since the inheritance chain stops at class without ever reaching to class which has a . Even if class don’t need to get anything from the inheritance (no args taken), it still need to do in order to pass packed to the next class on the chain.

The only way to work in multi-inheritance is to make sure all the classes implement the super cooperatively.

Hope you enjoy this blog. It can be pretty confusing when first learn about the inheritance stuff in python. But with some practice it should become a natural practice to your code.

Happy Reading!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store