Polymorphism

The Same function defined on the objects of different types is known as polymorphism.

One Name but multiple forms is the concept of polymorphism.

Example: The Best example for the polymorphism is the operator overloading."+ " acts as arithmetic addition operator and the string concatenation operator.

10+20=30  ##arithmatic addition
Apple+Juice=Applejuice ## string concatenation

Following are the related topics to discuss over polymorphism

1.Overloading :

  • Operator Overloading
  • Method Overloading
  • Constructor Overloading

2.Overriding :

  • Method Overriding
  • Constructor Overriding

3.Pythonic Behaviour :

  • Duck Typing
  • Easier to Ask forgiveness than permission
  • Monkey Patching

Operator Overloading

Using the same operator for multiple purposes is nothing but an operator overloading.

Python provides the support for the operator overloading, but Java Won't support for operator overloading.

Example: The following example demonstrates the use of operator overloading

class Book:
    def __init__(self,pages):
        self.pages=pages
        
    def __add__(self,other):  ##__add__ is the magic method for adding two book objects
        total_pages=self.pages+other.pages
        return total_pages
    
b1=Book(100)
b2=Book(200)
b3=Book(500)
print(b1+b2)
print(b1+b3)
print(b2+b3)

The output is:

operator-overloading-example

Operators and corresponding magic methods

Operator Magic Method
+ __add__()
_ __sub__()
* __mul__()
/ __div__()
// __floordiv__()
% __mod__()
** __pow__()
+= __iadd__()
-= __isub__()
*= __imul__()
/= __idiv__()
//= __ifloordiv__()
%= __imod__()
**= __ipow__()
< __lt__()
<= __le__()
> __gt__()
>= __ge__()
== __eq__()
!= __ne__()

Example : Overloading of > and <= operators for student class objects

class Student:
    def __init__(self,name,marks):
        self.name=name
        self.marks=marks
    def __gt__(self,other):
        return self.marks>other.marks
    
s1=Student("Ashu",100)
s2=Student("Ram",200)
s3=Student("Dimpi",40)
print(s1>s2)
print(s1>s3)   

The output is:

operator-overloading-example-two

Whenever we are implementing __gt__ () method automatically __lt__() method will be implemented

class Student:
    def __init__(self,name,marks):
        self.name=name
        self.marks=marks
    def __gt__(self,other):
        return self.marks>other.marks
    
s1=Student("Ashu",100)
s2=Student("Ram",200)
s3=Student("Dimpi",40)
print(s1>s2)
print(s1>s3) 
print(s1<s2)  

The output is:

implementing-less-than-operator

To use <= operator between the two student objects then we have implement the __le__() method.

class Student:
    def __init__(self,name,marks):
        self.name=name
        self.marks=marks
    def __gt__(self,other):
        return self.marks>other.marks
    def __le__(self,other):
        return self.marks<=other.marks
    
s1=Student("Ashu",100)
s2=Student("Ram",200)
s3=Student("Dimpi",40)
print(s1>s2)
print(s1>s3) 
print(s1<s2)  
print(s1<=s2)

The output is:

implementing-less-than-equal-operator

Example : The following program to demonstrate the overloading of the multiplication operator to work on employee objects.

class Employee:
    def __init__(self,name,salaryPerDay):
        self.name=name
        self.salaryPerDay=salaryPerDay
        
    def __mul__(self,other):
        return self.salaryPerDay*other.workingDays
    
class TimeSheet:
    def __init__(self,name,workingDays):
        self.name=name
        self.workingDays=workingDays
        
    def __mul__(self,other):
        return self.workingDays*other.salaryPerDay    
        
e=Employee("Abhinav",500)
t=TimeSheet("Abhinav",25)
print("This Month Salary:",e*t)
print("This Month Salary:",t*e)

The output is:

implementing-mul-method

Installing pydev with Eclipse

The __str__() Method

Whenever we are trying to print any object reference, internally __str__() method will be called, and the default representation of this method returns the string in the following format:

<__main__.Student object at 0x0000014F076EEEB8>

So, to provide a meaningful representation to our string object, we have to override the ___str__() method in our class.

Example:

class Student:
    def __init__(self,name,rollno,marks):
        self.name=name
        self.rollno=rollno
        self.marks=marks
        
        
s1=Student("Gagan",101,34)
s2=Student("Wilson",109,45)
print(s1)
print(s2)

The output is:

without-implementing-str-method

So, to get the meaningful string representation, we have to implement the __str__() method

class Student:
    def __init__(self,name,rollno,marks):
        self.name=name
        self.rollno=rollno
        self.marks=marks
        
    def __str__(self):
        #return self.name
        return "Name:{},RollNo:{},Marks:{}".format(self.name,self.rollno,self.marks)
        
      
s1=Student("Gagan",101,34)
s2=Student("Wilson",109,45)
print(s1)
print(s2)

The output is:

after-implementing-str-method

We can use + operator to add the two-book(b1+b2) objects, but when we use + operator to add three-book (b1+b2+b3) objects, python will throw an error.

class Book:
    def __init__(self,pages):
        self.pages=pages
        
    def __add__(self,other):  ##__add__ is the magic method for adding two book objects
        total_pages=self.pages+other.pages
        return total_pages
    
b1=Book(100)
b2=Book(200)
b3=Book(500)
print(b1+b2)
print(b1+b2+b3)

The output is:

error-while-adding-three-book-objects

This is because b1+b2 returns the result in int format, but we cannot use the concatenation operator (+) between int and string objects(int+b3)

So to overcome this problem, we can return the addition of two book objects into a book instead of int and then we can apply concatenation between [book(pages)+b3].

And to return the total number of pages in a meaningful format, we have to implement the __str__() method.

class Book:
    def __init__(self,pages):
        self.pages=pages
        
    def __add__(self,other):  ##__add__ is the magic method for adding two book objects
        return Book(self.pages+other.pages)
    
    def __str__(self):
        return "The Total Number of Pages:{}".format(self.pages)
    
    
b1=Book(100)
b2=Book(200)
b3=Book(500)
print(b1+b2)
print(b1+b2+b3)

The output is:

adding-three-book-objects-after-implementing-str-method

We can use the Multiplication operator(*) between the book objects; for this, we have to implement the __mul__() magic method.

class Book:
    def __init__(self,pages):
        self.pages=pages
        
    def __add__(self,other):  ##__add__ is the magic method for adding two book objects
        return Book(self.pages+other.pages)
    
    def __str__(self):  ##implementing strig magic method
        return "The Total Number of Pages:{}".format(self.pages)
    
    def __mul__(self,other):  ##implementing multiplicatio magic method
        return Book(self.pages*other.pages)
    
    
b1=Book(100)
b2=Book(200)
b3=Book(500)
b4=Book(600)
print(b1+b2*b3+b4)

The output is:

implementing-mul-method-between-book-objects

Type Casting

Method Overloading

The python won't support method overloading; hence if we declare multiple methods with the same name and a different number of arguments, because python is going to consider only the last method.

Example:

class Test:
    def method1(self):
        print("no argument")
        
    def method1(self,x):
        print("One argument")
        
    def method1(self,x,y):
        print("two argument")
        
t=Test()
t.method1()    

The output is:

method-overloading

If you consider the above output, python is considering the last method and throwing an error as it required two positional arguments x and y.

And if pass the two-argument value then the above program will execute

class Test:
    def method1(self):
        print("no argument")
        
    def method1(self,x):
        print("One argument")
        
    def method1(self,x,y):
        print("two argument")
        
t=Test()
t.method1(12,23)        

The output is:

python-considering-the-last-method-when-it -is-having-same-multiple-methods

Complex Datatype

Variable-Length Arguments Method

A method that accepts any number of arguments is called a variable-length argument.

The syntax for passing variable length arguments is :

def method1(self,*args)  ##*args is used to pass a variable number of arguments to a function.

Example

class Test:
    def method1(self,*args):
        print("Variable length arguments method")

t=Test()
t.method1()
t.method1(10)
t.method1(10,20)
t.method1(10,20,30)
t.method1(10,20,30,40)
t.method1(10,20,30,40,50)

The output is:

variable-length-argument

The following example demonstrates finding the sum of the numbers by using variable length arguments

class Test:
    def sum(self,*args):
        total=0
        for x in args:
            total=total+x
        print("The Sum:",total)
        

t=Test()
t.sum()
t.sum(10)
t.sum(10,20)
t.sum(10,20,30)
t.sum(10,20,30,40)
t.sum(10,20,30,40,50)

The output is:

finding-sum-of-numbers-by-using-variable-length-argument

Integer/int Datatype

Constructor Overloading

Within a single class, if we are trying to declare more than one constructor with a different number of arguments, then python won't support the constructor overloading.

Example:

class Test:
    def __init__(self):
        print("No-arg Constructor")
        
    def __init__(self,x):
        print("One-arg Constructor")
        
    def __init__(self,x,y):
        print("Two-arg constructor")
        
t=Test()        

The output is:

contructor-overloading

If we pass two-arguments, then the second constructor will execute.

class Test:
    def __init__(self):
        print("No-arg Constructor")
        
    def __init__(self,x):
        print("One-arg Constructor")
        
    def __init__(self,x,y):
        print("Two-arg constructor")
        
#t=Test()        
#t=Test(10)
t=Test(10,20)

The output is:

passing-two-arguments-to-the-constructor

Defining a Constructor with Variable Number of Arguments

We can define a constructor with a variable length of arguments in two ways

  • Constructor with Default Arguments
  • Constructor with a variable number of arguments

The Following example demonstrates defining a constructor with default arguments

class Test:
    def __init__(self,a=None, b=None, c=None):
        print("Constructor with 0|1|2|3 number of arguments")
        
t=Test()        
t=Test(10)
t=Test(10,20)

The output is:

defining-constructor-with-default-variable

The following example demonstrates defining a constructor with a variable number of arguments

class Test:
    def __init__(self,*args):
        print("Constructor with variable number of arguments")
        
t=Test()        
t=Test(10)
t=Test(10,20)
t=Test(10,20,30)
t=Test(10,20,30,40)
t=Test(10,20,30,40,50)
t=Test(10,20,30,40,50,60)

The output is:

defing-constructor-with-variable-length-argument

Classes in python

Method Overriding

The members present in the parent class are by default available to the child class through inheritance. Sometimes child class may not satisfy with parent class implementation; then child class is allowed to redefine that method based on its requirement; this process is called method overriding.

The Overriding concept is applicable only for Methods and constructor.

class Parent:
    def property(self):
        print("Land+Gold+Cash+Power")
        
    def marry(self):
        print("Anvitha")
        
class Child(Parent):
    pass

c=Child()
c.property()
c.marry()

The output is:

method-overriding-in-python

Now, the child class is not happy with the parent implementation, and the child is class is going to override the marry method.

class Parent:
    def property(self):
        print("Land+Gold+Cash+Power")
        
    def marry(self):
        print("Anvitha")
        
class Child(Parent):
    def marry(self):  ##overriding the marrry method in child class 
        print("Supriya")
    

c=Child()
c.property()
c.marry()

The output is:

method-overriding-inpython

If you want to call the parent class method also from the child class, then you can call by using the super() function, whenever we call parent method by using object reference then control will go to child class, and child class method will execute, but it will first execute the super() function and then the child class method.

class Parent:
    def property(self):
        print("Land+Gold+Cash+Power")
        
    def marry(self):
        print("Anvitha")
        
class Child(Parent):
    def marry(self):
        super().marry()
        print("Supriya")
    

c=Child()
c.property()
c.marry()

The output is:

calling-parent-nethod-and-child-method-in-overriding

A Python Constructor

Constructor Overriding

The members present in the parent class constructor are by default available to the child class through inheritance. Sometimes child class may not satisfy with parent class constructor implementation; then child class is allowed to redefine that constructor based on its requirement; this process is called constructor overriding.

The following example demonstrates the constructor overriding

class Parent:
    def __init__(self):
        print("Parent constructor")
        
        
class Child(Parent):
    pass
c=Child()

The output is:

constructor-overriding

We are overriding the parent class constructor from child class

class Parent:
    def __init__(self):
        print("Parent constructor")
        
        
class Child(Parent):
    def __init__(self):
        print("Child Constructor")
        
c=Child()

The output is:

overriding-parent-constructor-from-child-constructor

And also, from child class constructor we can call the parent class constructor by using the super() function.

class Parent:
    def __init__(self):
        print("Parent constructor")
        
        
class Child(Parent):
    def __init__(self):
        super().__init__()
        print("Child Constructor")
        
c=Child()

The output is:

calling-parent-class-constructor-from-child-class

The following example demonstrates the constructor overriding and method overriding

class Person:
    def __init__(self,name,age,height,weight):
        self.name=name
        self.age=age
        self.height=height
        self.weight=weight
        
    def display(self):
         print("Name:",self.name)
         print("Age:",self.age)
         print("Height:",self.height)
         print("Weight:",self.weight)
         
class Employee(Person):
    def __init__(self,name,age,height,weight,eno,esal):     ###constructor overriding
        super().__init__(name,age,height,weight)       ##from child class constructor calling parent class constructor
        self.eno=eno
        self.esal=esal
        
    def display(self):  ##from child class overriding method
        super().display()   ##calling parent class method
        print("Employee Number:", self.eno)
        print("Employee Salary:",self.esal)
        
e=Employee("Raju", 48,5.3,67,456789,49000)
e.display()        

The output is:

the-program-expl-constructor-overiding-and-method-overiding

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions