   ## 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

2.Overriding :

• Method Overriding
• Constructor Overriding

3.Pythonic Behaviour :

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

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

```class Book:
def __init__(self,pages):
self.pages=pages

total_pages=self.pages+other.pages

b1=Book(100)
b2=Book(200)
b3=Book(500)
print(b1+b2)
print(b1+b3)
print(b2+b3)```

The output is: #### 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: 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: 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: 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: ## 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: 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: 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

total_pages=self.pages+other.pages

b1=Book(100)
b2=Book(200)
b3=Book(500)
print(b1+b2)
print(b1+b2+b3)```

The output is: 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

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: 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

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: 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: 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: ## 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: 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: 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: 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: ## 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: 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: ## 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: 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: 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: ## 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: 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: 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: 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: Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions