21. OOP ‐ Inheritance Polymorphism - MantsSk/CA_PTUA14 GitHub Wiki

Introduction

As we already know, Python is an Object-Oriented Programming language. Everything in Python is an object. Like other Object-Oriented languages, when creating objects using classes, there are 4 basic principles for writing clean and concise code. These principles are called the four pillars of object-oriented programming (OOP). These four pillars are: Inheritance, Polymorphism, Encapsulation and Abstraction.

Today we are focusing on Inheritance and Polymorphism

Why OOP?

A few to name:

  • It makes it so much easier to maintain and update existing code.
  • Provides code reusability.
  • Provides a modular structure.
  • It is easy to debug.

Inheritance/Polymorphism

Inheritance

Inheritance is the process by which one class inherits the properties of another class. This newly formed class is called a child class and the other class is called a parent class. It provides code reusability because we are using an existing class to increase the properties of a new class. A child class can access all the data members and functions of the parent class.

In Python, to inherit a class, we use ChildClass(ParentClass) at the time of defining the class: 🔽

class Employee:                     
    def __init__(self, name, age, exp, salary):                          ## Defining The Constructor With Common data
        ## Instance Attributes                                       ## instance attributes that are only accessible by the object of the class
        self.name = name                                              
        self.age = age
        self.exp = exp
        self.salary = salary

    def show(self):                                                   ## A Simple method that prints the data 
        print(self.name,self.age,self.exp,self.salary)                ## Printing all the variables


class Engineers(Employee):                                            ## Parent class Employee inherit by child Enginners

    def __init__(self, name, age, exp, salary, level):                     ## Constructor of Enginners class(child)
        super().__init__(name,age,exp,salary)                         ## accessing the parent class constructor and instance attributes with the help of super method
        self.level = level                                            ## Assigning a new attribute level for the Enginner class


    def print_data(self):                                             ## Creating a New Method for the Enginner class that is only accessable by the object of Enginner class
        print(self.level)                                             ## Printing the level of the Enginner



class Designers(Employee):                                           ## Parent class Employee inherit by child Designers

        def __init__(self,name,age,exp,salary,position):               
            super().__init__(name,age,exp,salary)                    
            self.position = position                                 ## Extending the attributes of parent class by adding a new attribute position
        
        def show(self):                                              ## A Simple Method Belong to the Designer Class that is used to print the data
            super().show()                                           ## Accessing parent class method and extending it to print the position also
            print(self.position)                                     ## Printing the position of the Designer


obj_E = Engineers("Alex",35,10,50000,'JR Developer')                ## Creating an object for the Enginners class, Need to paas arguments because It is a inherit class

obj_E.show()                                                        ## accessing parent method show with the help of the child class object
obj_E.print_data()                                                  ## accessing child class method debug, that is only access by the Designers object

obj_D = Designers("Karl",45,20,55000,"UI Designer")                 
obj_D.show()                                                        ## printing all the data 

'''
obj_E.debug()                                                       ## AttributeError: 'Engineers' object has no attribute 'debug'  ,debug is not a member of the Enginners class
Employee('Garry',35,10,50000).show()                                ## Garry 35 10 50000   ,because a class can access class attributes without the need of an object
obj = Employee('Garry',35,10,50000)
obj.debug()                                                         ## AttributeError: 'Employee' object has no attribute 'debug' , A parent can not access a child class
'''

At first, we have a parent class Employee that has some basic information: name, age, exp (experience), and salary. Employee class has a constructor that contains all the instance attributes. Lastly, we also have a method named show that only has a print statement and is used for printing the information. Engineer and Designer both are the child classes of the parent class Employee. The ** super() method** helps a child class to access the members of the parent class. Engineers class accesses name, age, exp, and salary information from the parent class.

Polymorphism

Polymorphism means having different forms. It refers to the ability of a function with the same name to carry a different functionality altogether. One of the best examples of inbuild polymorphism is the len() function. When we use it for a list, it returns the count of number elements in the list. When we use it with a dictionary, it returns the count of keys. When we use it with a string, it returns the number of characters in the string. Let’s see an example of polymorphism. Simple example: ⏬

def add(x, y, z = 0):
    return x+y+z

print(add(5, 6))
print(add(5, 7, 4)) 
----------OUTPUT--------------
11
16

The above code shows how a single function can be used to perform two number and three number addition at the same time. Now let’s see polymorphism in a class method:

class Rectangle:
    def __init__(self, l, b):
        self.l = l
        self.b = b

    def area(self):
        return self.l * self.b

class Square:
    def __init__(self, side):
        self.s = side

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

rec = Rectangle(10, 20)
squ = Square(10)
for data in (rec, squ):
    print(data.area())    
    
-----------------------------OUTPUT----------------------------
200
100

In the code above, area() is performing two tasks in different instances of the program. On one side it is calculating the area of the rectangle, and on the other side it is calculating the area of the square in the same program.

Exercises:

🧠 : Repeat the Conditional Statements, Loops, Functions to finish these task.

  1. Create a parent class Animal with a method speak() that returns a string. Then, create two child classes Dog and Cat that inherit from Animal. Override the speak method in each of the child classes to return unique sounds that these animals make.

  2. Create a class Person with a constructor that takes name and age as parameters. Then, create a subclass Student which also takes grade as an additional parameter. Ensure that Student uses the Person class's constructor to handle name and age.

  3. Create a function describe_pet that takes an object as a parameter and calls the speak method of the passed object. Define two classes Bird and Fish. Each class should have its own implementation of speak method. Demonstrate polymorphism by passing instances of Bird and Fish to describe_pet.

🌐 Extra reading (or watching 📺 ):