The super() function is used to access the superclasses of a given class in the inheritance hierarchy.

When you call an attribute on an object, Python  looks for the attribute on the object itself, then looks in each of its parent classes in turn, until it finds a match. If the superclasses are exhausted and the attribute hasn't been found, an AttributeError exception is raised.

Let’s say we have a base class that defines a method we want to use in subclassed objects. By default, the subclass inherits the method as it is from the super class.

class SuperClass:
    def __init__(self):
       pass
    def example_method(self):
        print('Hello, World!')

class SubClass(SuperClass):
    def __init__(self):
         pass

c = SubClass()
c.example_method()

Sometimes we want the subclass to have a similar method, but with additional functionality.The super() function allows us to achieve this without having to rewrite the method from scratch.

super() # Same as super(__class__, <first_argument>)
super(type) # unbound super object
super(type, obj) # bound super object; requires isinstance(obj, type)
super(type, type2) # bound super object; requires issubclass(type2, type)

When called without arguments (i.e super() ) it returns a temporary object of the superclass, allowing you to call methods from the superclass. This is commonly used in single-inheritance scenarios.

When called with arguments (i.e super(type, object)), it returns a temporary object of the superclass of type type (or a superclass that is a subclass of type) and associated with object. This form of super() is used in multiple-inheritance scenarios to explicitly specify the superclass to invoke methods from.

class ParentClass:
    def __init__(self):
        pass

    def example_method(self):
        print('Parent Method')

class ChildClass(ParentClass):
    def __init__(self):
        pass

    def example_method(self):
        super().example_method()
        print('Modified Functionality.')

child_obj = ChildClass()
child_obj.example_method()

Invoking the super(). __init__() method inside the ChildClass's __init__ method  initializes the parent attributes before initializing the child attributes.

class Human:
    def __init__(self, name, age): 
        self.name = name
        self.age = age

class Student(Human):
    def __init__(self, name, age, school):
          super().__init__(name, age) 
          self.school = school 

s = Student('Mike', 20, 'Reeds')
print(s.name)
print(s.age)
print(s.school)
class Animal:
    def __init__(self, name, species): 
        self.name = name
        self.species = species 

class Lion(Animal):
      def __init__(self, name, species, roar):
          super(Lion, self).__init__(name, species)
          self.roar = roar

lion = Lion("Simba", "Lion", "Purrggg!!!")
print(lion.name)
//Simba
print(lion.species)
//Lion
print(lion.roar)
//Purrggg!!!

In the Lion class's __init__ method, super(Lion, self).__init__(name, species) is used to call the __init__ method of the Animal class, passing the name and species arguments.