Polymorphism in Python
Polymorphism = Poly (means Many) + morph (means Form) + ism (means to be or being). So, Polymorphism means to be in or to take multiple forms. Objects takes multiple forms. Hence, Objects exhibit Polymorphism.
The concept of Polymorphism is used in software development when it comes to:
- Loose Coupling: In software systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services.
- Dependency Injection: It is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used as a service. An injection is the passing of a dependency to a dependent object, a client that would use it.
- Interface: An interface is a shared boundary across which two or more separate components of a computer system exchange information, where the exchange can be between software, computer hardware, peripheral devices, humans and combinations of these.
- Duck Typing
- Operator Overloading
- Method Overloading
- Method Overriding
Duck Typing in Python
There is a famous line, "If there is a bird, which is walking like a duck, quaking like a duck, and swimming like a duck, that bird is a duck." We apply the concept in Python programming too through a notation called Duck Typing, which is one kind of manifestation of Dynamic Typing:
Dynamic Typing: When we say x= 3 (int) or x = 4.5 (float) or ‘Navin’ (str), unlike in other programming languages, in Python x is just name of the memory used to store the Type. It does not matter what is the type to x.
Similarly, if there is an object, which is “ide” (shown in the examples below), and it has a method “execute”, it does not matter what class object it is. What matters is, it should have the method “execute”. This is called Duck Typing in Python.
In the code below Object “ide” Type is not fixed to the Class PyCharm:
# Demonstrating Duck Typing
class PyCharm: # PyCharm does two stuffs
def execute(self):
print("Compiling")
print("Running")
class MyEditor: # MyEditor does extra stuffs
def execute(self):
print("Spell Check")
print("Convention Check")
print("Compiling")
print("Running")
class Laptop:
def code(self, ide):
ide.execute()
ide = PyCharm() # ide value is not fixed
lap1 = Laptop()
lap1.code(ide)
Result:
Compiling
Running
The Object “ide” Type is changed to the Class MyEditor
# Demonstrating Duck Typing
class PyCharm: # PyCharm does two stuffs
def execute(self):
print("Compiling")
print("Running")
class MyEditor: # MyEditor does extra stuffs
def execute(self):
print("Spell Check")
print("Convention Check")
print("Compiling")
print("Running")
class Laptop:
def code(self, ide):
ide.execute()
ide = MyEditor() # ide value is not fixed
lap1 = Laptop()
lap1.code(ide)
Result:
Spell Check
Convention Check
Compiling
Running
Operator Overloading
The operators like +, -, *, /, etc., are used to perform the respective operation. Behind the scene the operators work as methods that they called as Magic Methods. For example:
- The ‘+’ operator calls the ‘__add__()’ method, to add two values
- The ‘-’ operator calls the ‘__sub__()’ method, to find the difference between two values
- The ‘*’ operator calls the ‘__mul__()’ method, to multiply two values, and so on...
These in-built methods have fixed or pre-defined types. So, you can add two int numbers, like 5 and 6 or concatenate two stings like ‘Hello’ and ‘World’. But you can’t add an int number with a string, like 5 and ‘World’. However, if we create our own method, using the in-built method, to add or operate on two custom types, then that is called Operator Overloading. This is something like polymorphism of the in-built methods.
Few examples of overriding the in-built __add__(), __gt__(), and __str__() methods:
# Demonstrating Operator Overloading for addition method
class Student:
def __init__(self,m1,m2):
self.m1 = m1
self.m2 = m2
def __add__(self, other): # Custom __add__() (addition) method
m1 = self.m1 + other.m1
m2 = self.m2 + other.m2
m3 = Student(m1,m2)
return m3
s1 = Student(58,69)
s2 = Student(60,65)
s3 = s1 + s2
print(s3.m1)
print(s3.m2)
Result:
118
134
# Demonstrating Operator Overloading for greater than and string method
class Student:
def __init__(self, m1, m2):
self.m1 = m1
self.m2 = m2
def __gt__(self, other): # Custom __gt__() (greater than) method
r1 = self.m1 + self.m2
r2 = other.m1 + other.m2
if r1 > r2:
return True
else:
return False
def __str__(self): # Custom __str__() (string) method
return '{} {}'.format(self.m1, self.m2)
s1 = Student(58, 69)
s2 = Student(60, 75)
if s1 > s2:
print('s1 wins with marks', s1)
else:
print('s2 wins with marks', s2)
Result:
s2 wins with marks 60 75
Method Overloading and Method Overriding
There are two type of Polymorphism in Python – Method Overriding and Method Overloading:
Method Overloading:
Languages like Java, C Sharp or any other OOPs languages have the concept of Method Overloading, which is not there in Python. If two methods have same name, say average() but different parameters like average(a,b) and average(a,b,c) this is called method of Overloading. In Python, we can’t create two methods with same name but different parameters. But we can create Method Overloading in Python using some trick.
Method Overriding:
Method Overriding means two methods with the same name and same parameters or arguments. Again, in Python we can’t create such methods. But separately using Inheritance we can create Method Overriding.
Method Overriding is used heavily in software development.
# Demonstrating Method Overloading
class Student:
def __init__(self,name):
self.name = name
def sum(self,a=None,b=None,c=None): # Overloading method of sum(a,b) with sum(a,b,c)
s = 0
if a!=None and b!=None and c!=None:
s = a+b+c
elif a!=None and b!=None:
s = a+b
else:
s = a
return s
s1 = Student('Nitin')
print(s1.sum(5,7,9))
Result:
21
Empty class B inherits A
# Demonstrating Method Overriding
class A:
def show(self):
print("In A Show")
class B(A):
pass
a1 = B()
a1.show()
Result:
In A Show
Class B with own method implements self
# Demonstrating Method Overriding
class A:
def show(self):
print("In A Show")
class B(A):
def show(self):
print("In B Show")
a1 = B()
a1.show()
Result:
In B Show
Like, if you have your phone you will show your phone. Otherwise you will show your father’s phone.
Similarly, from above example it is clear that when you call the show function, it will call the show method of the Sub class if it has a method. Otherwise it will call the show method of its Super class.
MultiThreading in Python
We break down a big task into small processes, and then each process into threads. So, thread is a small and lightweight process. Threads run parallelly. Different processes or threads running simultaneously through MultiThreading can be viewed in Windows system in the Performance tab under Task Manager, as shown below.
The program below shows how through MultiThreading processes run parallelly.
# Demonstrating MultiThreading
from time import sleep
from threading import *
class Hello(Thread):
def run(self):
for i in range(5):
print("Hello")
sleep(1)
class Hi(Thread):
def run(self):
for i in range(5):
print("Hi")
sleep(1)
t1 = Hello() # t1 Thread
t2 = Hi() # t2 Thread
t1.start()
sleep(0.2) # Forcing a gap of 0.2 sec between t1 and t2
t2.start()
t1.join() # Allowing t1 and t2 completes their tasks
t2.join()
print("Bye") # Main Thread
Result:
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Bye
Note that the strings are printed in alternate turns, which shows that both the objects t1 and t2 are running simultaneously.