Skip to main content

Inheritance

Inheritance is a fundamental concept in object-oriented programming, which allows a class to inherit properties and methods from another class. There are several types of inheritance, including:



Single Inheritance: In single inheritance, a subclass inherits properties and methods from a single parent class. The subclass is said to be derived from the parent class.


Multiple Inheritance: Multiple inheritance allows a subclass to inherit properties and methods from multiple parent classes. In this case, the subclass is said to have multiple base classes. However, multiple inheritance can lead to complexity and ambiguity in the code.


Multilevel Inheritance: Multilevel inheritance occurs when a subclass inherits properties and methods from a parent class, which in turn inherits from another parent class. In this case, the subclass is said to be derived from both the parent class and the grandparent class.


Hierarchical Inheritance: Hierarchical inheritance occurs when multiple subclasses inherit properties and methods from a single parent class. In this case, the parent class is said to be the base class of all the subclasses.


Hybrid Inheritance: Hybrid inheritance is a combination of multiple inheritance and hierarchical inheritance. In this case, a subclass can inherit properties and methods from multiple parent classes, some of which may also be base classes for other subclasses.



Note: Each type of inheritance has its own advantages and disadvantages, and the choice of inheritance type depends on the specific requirements of the programming task at hand.


Important points about using Inheritance in Python:

 

  • Inheritance allows a new class to be based on an existing class, inheriting all of its attributes and methods.
  • In Python, a subclass can inherit from one or more parent classes, creating single, multiple, multilevel, hierarchical or hybrid inheritance relationships.
  • The subclass can override or extend the behavior of the parent class methods.
  • Python supports the super() function to call a method from a parent class, which is useful when you want to override the behavior of the parent method but still use some of its functionality.
  • Inheritance allows for code reuse, as common functionality can be encapsulated in a parent class and inherited by multiple child classes.
  • When a method is called on a subclass object, Python searches for the method in the subclass, and then in its parent classes in the order specified by the MRO (Method Resolution Order) algorithm.
  • Multiple inheritance can lead to complex relationships between classes, so care must be taken to avoid issues like diamond inheritance and method name conflicts.
  • Inheritance can help to make code more flexible, allowing for customization and extension of existing classes.

super() function

super() is a built-in function in Python that provides a way to access and call methods in a parent class from a subclass. It allows a subclass to inherit attributes and methods from its parent class and then customize or override them.

Using super(), a subclass can call a method in its parent class without explicitly naming the parent class, which can make the code more flexible and easier to maintain. This is especially useful in cases where the parent class may change in the future, or when multiple inheritance is involved.

In simple terms, super() allows a subclass to reuse and build upon the functionality of its parent class. It is a powerful tool that enables efficient code reuse and promotes good software design practices.

********************************************************************************

Scenario: Shapes are a basic concept in geometry, and different types of shapes have different attributes and methods. For example, a circle has a radius and a method to calculate its area, while a square has a side length and a method to calculate its perimeter.

Exercise: Create a base class called "Shape" with methods to calculate the area and perimeter of a shape. Create derived classes for each type of shape with additional attributes and methods specific to their shape. Create instances of each type of shape and test their specific methods.

import math

class Shape:
    def area(self):
        pass

    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

class Square(Shape):
    def __init__(self, side_length):
        self.side_length = side_length

    def area(self):
        return self.side_length ** 2

    def perimeter(self):
        return 4 * self.side_length

class Rectangle(Shape):
    def __init__(self, l, w):
        self.length = l 
        self.width = w
        
    def area(self):
        return  self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)

# Create instances of each type of shape
circle = Circle(5)
square = Square(10)
rectangle = Rectangle(6, 8)

# Test specific methods for each type of shape
print(circle.area())            # Output: 78.53981633974483
print(square.perimeter())       # Output: 40
print(rectangle.area())          # Output: 48.0
print(rectangle.perimeter())     # Output: 28


********************************************************************************

Scenario: A company has multiple types of vehicles such as cars, trucks, and motorcycles. All vehicles have a make, model, year, and color. However, each type of vehicle has additional attributes and functions specific to their type. For example, cars have a number of doors, trucks have a towing capacity, and motorcycles have a top speed.

Exercise: Create a base class called "Vehicle" with attributes for make, model, year, and color. Create derived classes for each type of vehicle with additional attributes and functions specific to their type. Create instances of each type of vehicle and test their specific functions.

class Vehicle:
    def __init__(self, make, model, year, color):
        self.make = make
        self.model = model
        self.year = year
        self.color = color

class Car(Vehicle):
    def __init__(self, make, model, year, color, num_doors):
        super().__init__(make, model, year, color)
        self.num_doors = num_doors

    def honk(self):
        print("Beep beep!")

class Truck(Vehicle):
    def __init__(self, make, model, year, color, towing_capacity):
        super().__init__(make, model, year, color)
        self.towing_capacity = towing_capacity

    def tow(self):
        print(f"Towing with {self.towing_capacity} lbs capacity.")

class Motorcycle(Vehicle):
    def __init__(self, make, model, year, color, top_speed):
        super().__init__(make, model, year, color)
        self.top_speed = top_speed

    def wheelie(self):
        print("Popping a wheelie!")

# Create instances of each type of vehicle
my_car = Car("Toyota", "Corolla", 2021, "red", 4)
my_truck = Truck("Ford", "F-150", 2022, "blue", 8000)
my_motorcycle = Motorcycle("Honda", "CBR600RR", 2020, "black", 160)

# Test specific functions for each type of vehicle
my_car.honk()        # Output: "Beep beep!"
my_truck.tow()       # Output: "Towing with 8000 lbs capacity."
my_motorcycle.wheelie() # Output: "Popping a wheelie!"

********************************************************************************

Scenario: A company has multiple types of employees such as developers, designers, and managers. All employees have a name, age, and salary. However, each type of employee has additional attributes and functions specific to their role. For example, developers have a programming language they specialize in, designers have a portfolio of work they've created, and managers have a team they manage.

Exercise: Create a base class called "Employee" with attributes for name, age, and salary. Create derived classes for each type of employee with additional attributes and functions specific to their role. Create instances of each type of employee and test their specific functions.

class Employee:
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary
    
    def get_info(self):
        return f"Name: {self.name}, Age: {self.age}, Salary: {self.salary}"
        

class Developer(Employee):
    def __init__(self, name, age, salary, programming_language):
        super().__init__(name, age, salary)
        self.programming_language = programming_language
    
    def get_info(self):
        return f"{super().get_info()}, Programming Language: {self.programming_language}"
        

class Designer(Employee):
    def __init__(self, name, age, salary, portfolio):
        super().__init__(name, age, salary)
        self.portfolio = portfolio
    
    def get_info(self):
        return f"{super().get_info()}, Portfolio: {self.portfolio}"
        

class Manager(Employee):
    def __init__(self, name, age, salary, team):
        super().__init__(name, age, salary)
        self.team = team
    
    def get_info(self):
        return f"{super().get_info()}, Team: {self.team}"
        

# Create instances of each type of Employee
dev = Developer("Megha", 30, 80000, "Python")
print(dev.get_info()) # Name: Megha, Age: 40, Salary: 150000, Programming Language: Python

designer = Designer("Jei", 28, 75000, "https://jmndesigns.com")
print(designer.get_info()) # Name: Jei, Age: 28, Salary: 75000, Portfolio: https://jmndesigns.com

manager = Manager("Navan", 45, 100000, "Marketing")
print(manager.get_info()) # Name: Navan, Age: 45, Salary: 200000, Team: Marketing

********************************************************************************


Scenario: A person has basic attributes such as name, age, and gender. However, there are different types of people with additional attributes and functions specific to their type. For example, an employee has a salary, a manager has a department, and a customer has a purchase history.

Exercise: Create a base class called "Person" with attributes for name, age, and gender. Create derived classes for each type of person with additional attributes and functions specific to their type. Create instances of each type of person and test their specific functions.

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

class Employee(Person):
    def __init__(self, name, age, gender, salary):
        super().__init__(name, age, gender)
        self.salary = salary

    def get_salary(self):
        return self.salary

class Manager(Person):
    def __init__(self, name, age, gender, department):
        super().__init__(name, age, gender)
        self.department = department

    def get_department(self):
        return self.department

class Customer(Person):
    def __init__(self, name, age, gender, purchase_history):
        super().__init__(name, age, gender)
        self.purchase_history = purchase_history

    def get_purchase_history(self):
        return self.purchase_history

# Create instances of each type of person
employee = Employee("Megha", 40, "male", 50000)
manager = Manager("Jei", 35, "female", "Sales")
customer = Customer("Navan", 20, "female", ["book", "pen", "shirt"])

# Test specific functions for each type of person
print(employee.get_salary())              # Output: 50000
print(manager.get_department())           # Output: "Sales"
print(customer.get_purchase_history())    # Output: ["book", "pen", "shirt"]

********************************************************************************


MRO (Method Resolution Order)
  • MRO determines the order in which Python looks up and calls methods in a class hierarchy.
  • When a method is called on an object, Python looks for that method in the object's class and then its parent classes, up the hierarchy.
  • Python uses the C3 linearization algorithm to calculate the MRO, which takes into account the order of inheritance and ensures that no class is searched before all its parent classes have been searched.
  • You can view the MRO for a class by calling the mro() method on the class.
  • It's important to understand MRO to correctly implement inheritance and resolve method name clashes in complex class hierarchies.


Write a Python program to demonstrate MRO in multiple inheritance. Create a class A with a method foo() that prints "A foo", and create two subclasses B and C that both inherit from A. B should override foo() to print "B foo", and C should override foo() to print "C foo". Finally, create a subclass D that inherits from both B and C, and call foo() on an instance of D. Verify that the method resolution order follows the MRO.

class A:
    def foo(self):
        print("A foo")

class B(A):
    def foo(self):
        print("B foo")

class C(A):
    def foo(self):
        print("C foo")

class D(B, C):
    pass

# Create an instance of D
d = D()

# Call the foo() method on the instance of D
d.foo()

# Print the method resolution order for D
print(D.mro())

In this program, we first create a base class A with a method foo() that prints "A foo". We then create two subclasses B and C that both inherit from A. B overrides foo() to print "B foo", and C overrides foo() to print "C foo". Finally, we create a subclass D that inherits from both B and C.

We then create an instance of D and call the foo() method on it. Since D inherits from both B and C, and both of these classes override the foo() method, the method resolution order determines which version of foo() gets called. In this case, since D inherits from B before C, the version of foo() defined in B gets called, and "B foo" is printed.

Finally, we print the method resolution order for D using the mro() method, which returns a list of the class and its ancestors in the order they will be searched for methods. In this case, the method resolution order for D is [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>], which shows that Python first looks for methods in D, then in B, then in C, then in A, and finally in the base class object. This order follows the C3 linearization algorithm used by Python to determine the MRO for classes with multiple inheritance.

********************************************************************************


Comments

Popular posts from this blog

Python OOPs Concepts: Using Variables and Methods

  Types of Variables in OOPs Python   Instance Variable Static Variable Local Variable   Object Level Variables Class Level Variables Method Level Variables When to use: For Every Object if you want Separate copy, use Instance Variables For all object one copy is required, use static variables Inside method, Just used for temporary requirement Where to Declare Inside the constructor method (in general) Within the class directly, outside of methods (in general)   Within the method only. How to Declare Within the constructor: Instance variables can be declared within the constructor method using the self .   Using default values : Instance variables can be assigned default values during initialization.   Outside the class: use object name.   ·          Within the class directly

Polymorphism: Method Overloading vs Method Overriding

  Method Overloading In object-oriented programming languages, method overloading enables a class to have several methods with the same name but different parameters. However, in Python, method overloading is not directly supported as opposed to languages such as Java or C++. This is because Python allows developers to define default arguments for their methods and pass arguments of any type to a method. This flexibility allows a single method to handle various types of arguments, eliminating the need for overloading.   However, there is a way to simulate method overloading in Python by using default argument values or variable length arguments and conditional statements. Here's an example: Program using default arguments:       Program using variable length arguments:   Multiple methods with Same Name: When we define multiple methods with same name, Python will consider the last defined method only. Python will not support method overloading. ( Why? Method overlo