Python | super () in single inheritance

At a rather abstract level, super () provides access to those methods of the superclass (parent class) that have been overridden in the subclass (child class) that inherits from it. Consider the example code below, here we have a class named Square and another class named Cube which inherits from Square .

class Square:

def __ init __ ( self , side):

  self .side = side

 

def area ( self ):

return self . side * self . side

 

class Cube (Square):

def area ( self ):

face_area = self . side * self . side

return face_area * 6

 

  def volume ( self ):

face_area = self . side * self . side

return face_area * self . side

Note: Since the Cube class does not have a __init __ () method, the __init __ () from the Square class will be used to initialize Cube instances (the main inheritance property).

Looking at this example, we know that each face of the cube is a square, and therefore the face_area Cube represents the area of the square. Now it makes sense to evaluate face_area using the area () method of the Square class rather than calculating it manually, this will not only save us from rewriting the code, but also will allow you to change the logic of the area () from one place. But since we have overridden the area () method in Cube , we cannot call the area () method on Square using self.area ()
Now, this is a situation where super() comes to the rescue.  super () returns the proxy object of the parent class and then you call a method of your choice on that proxy object, this way we can call the area () method Square class using super () as super().area() Here follows the modified definition of class Cube .

class Cube (Square):

def area ( self ):

return super (). area () * 6

  

  def volume ( self ):

return super (). area () * self . side ()

Note that we could have named area () Square as Square.area () and not super (). area () Square.area () , but it`s easier for the last creator to change the superclass or rename it as needed, making the code much easier to maintain.

Passing Arguments to super ()
In the previous section, we discussed how to use super () without any either parameters, but this only gives us access to the methods of this soup of the class that is the immediate parent of the subclass.

To access the methods of this superclass, which is not the immediate parent of the subclass, we use super () with two arguments. Let`s look at an example of three classes named Square , SquarePrism and Cube to understand how to use super () with arguments ... 
Now the cube — it is simply a special type of square prism whose height is equal to the side of its base, and therefore the cube resembles a square prism much more than a square. Therefore, in this example, class Cube will inherit class SquarePrism and class
SquarePrism
will inherit class Square . For the Square class, we will use the same definition that we used in the previous section. Below is the definition of our newly defined SquarePrism class.

class SquarePrism (Square ):

def __ init __ ( self , side, height):

self . side = side

  self . height = height

 

def face_area ( self ):

  base_area = super (). area ()

lateral_area = self . side * self . height

return base_area, lateral_area

  

  def area ( self ):

base_area = super (). area ()

  lateral_area = self . side * self . height

return 2 * base_area + 4 * lateral_area

Instance SquarePrism has two attributes: the side of its square base and the height of the square prism. The face_area () instance method returns a tuple of two numbers representing the base area of ​​the square prism and the side area of ​​the square prism. Since the base is a square, for the base area of ​​a square prism, we name the Square.area () method as super (). Area () Square.area () super (). area () . The area () method returns the total surface area of ​​the square prism.

So far we have used super () without any parameters, now a new one follows. a Cube class that will demonstrate using super () with parameters.

class Cube (SquarePrism):

def __ init __ ( self , side)

super () .__ init __ (side = side, height = side)

 

  def face_area ( self ):

return super (SquarePrism, self ). area ( )

 

def area ( self ):

return super (). area ()

Unlike the __init __ () and area () methods, the face_area () Cube method is slightly different from its counterpart SquarePrism.face_are a () . For a cube, the side area is equal to the base area and therefore it makes no sense for face_area () to return a tuple, so face_area () class Cube will return the area one of the faces of the cube. ,
Now, since each face of the cube is a square, it makes sense again to use the area () class Square method. Now, since class Square is not the immediate parent of class Cube , we cannot access the area () class Square as super (). area () SquarePrism.area () it will instead call SquarePrism.area () .

Here we use super (SquarePrism, self) .face_area () to call the area () class Square method . In the first argument, SquarePrism means that super () looks for the area () method in the immediate parent of the SquarePrism class which located in class Square . Using self as the second parameter provides the context of the current Cube object for super () for the requested area () method.

Remember that using super () in a two-argument form requires that the object passed as the second argument be an instance of the type passed to as the first argument.

Note: Because the Cube class is a child of the class SquarePrism , the Cube instance is also an instance of class SquarePrism and class Square .

Try the following code snippet and view the output to clarify the above point.

class Square:

  def __ init __ ( self ):

pass

 

class SquarePrism (Square):

def __ init __ ( self ):

pass

 

class Cube ( SquarePrism):

def __ init __ ( self ):

  pass

  

square = Square ()

squareprism = SquarePrism ()

cube = Cube ()

 

print ( isinstance (squareprism, Square))

print ( isinstance (cube, Square))

It`s worth noting that the super () with a null argument can only be used inside the class definition, since it is automatically filled in by the compiler with the appropriate parameters, i.e. if we using super () inside a class say X , super () will be converted to super (X, self) the compiler.

Although the null-argument super () form is popular with developers, the two-argument form is not very useful. But in multiple inheritance, the two-argument form plays a very important role.