Python Programming

python polymorphism python Operator Overloading and Magic Methods

Polymorphism

Python Polymorphism– Poly means many and morphism means forms. Python Polymorphism is one of the tenets of Object Oriented Programming (OOP). Python Polymorphism means that you can have multiple classes where each class implements the same variables or methods in different ways. Python Polymorphism takes advantage of inheritance in order to make this happen. A real-world example of python Polymorphism is suppose when if you are in classroom that time you behave like a student, when you are in market at that time you behave like a customer, when you are at your home at that time you behave like a son or daughter, such that same person is presented as having different behaviors. Python is a dynamically-typed language and specifically uses duck-typing. The term duck-typing comes from the idiomatic saying, “If it looks like a duck and quacks like a duck, it is probably a duck.” Duck-typing in Python allows us to use any object that provides the required methods and variables without forcing it to belong to any particular class. In duck-typing, an object’s suitability is determined by the presence of methods and variables rather than the actual type of the object. To elaborate, this means that the expression some_obj.foo() will succeed if object some_obj has a foo method, regardless of to which class some_obj object actually belongs to. Difference between inheritance and polymorphism

is, while inheritance is implemented on classes, python Polymorphism is implemented on methods.


Amazon Purchase Links:

Top Gaming Computers

Best Laptops

Best Graphic Cards

Portable Hard Drives

Best Keyboards

Best High Quality PC Mic

Computer Accessories

*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!

Program 1: Program to Demonstrate  Python Polymorphism:

class Vehicle:
    
    def __init__(self, model):
        self.model = model
    def vehicle_model(self):
        print(f"Vehicle Model name is {self.model}")
class Bike(Vehicle):
    def vehicle_model(self):
        print(f"Vehicle Model name is {self.model}")
class Car(Vehicle):
    def vehicle_model(self):
        print(f"Vehicle Model name is {self.model}")

class Aeroplane:
    
    pass

def vehicle_info(vehicle_obj):
    vehicle_obj.vehicle_model()
def main():
    ducati = Bike("Ducati-Scrambler")
    beetle = Car("Volkswagon-Beetle")
    boeing = Aeroplane()
    for each_obj in [ducati, beetle, boeing]:
    
        try:
            vehicle_info(each_obj)
        except AttributeError:
            print("Expected method not present in the object")
if __name__ == "__main__":
    main() 

Output
Vehicle Model name is Ducati-Scrambler
Vehicle Model name is Volkswagon-Beetle
Expected method not present in the object

python polymorphism

Even though each of these methods in the classes have the same name, their implementation details are different. Polymorphic behavior allows you to specify common methods in a base class and implement them differently in other derived classes. In this program, we defined two derived classes, Bike and Car, inherited from vehicle class, and provided their own implementation of vehicle_model() method on top of vehicle_model() method found in

Vehicle class. Notice that all the classes have vehicle_model() method but they are implemented differently. The method vehicle_model() is polymorphic, as these methods have the same name but belong to different classes and are executed depending on the object. The behavior of the same method belonging to different classes is different based on the type of object. To allow python Polymorphism, a common interface called vehicle_info() – function is created

that can take an object of any type and call that object’s vehicle_model() method. When you pass the objects ducati and beetle to the vehicle_info() function, it executes vehicle_model() method effectively. Because of python Polymorphism, the run-time type of each object is invoked. In the for loop, you iterate through each object in the list using each_obj

as the iterating variable. Depending on what type of object it has, the program decides which methods it should use. If that object does not have the methods that are called, then the function signals a run-time error. If the object does have the methods, then they are executed no matter the type of the object, evoking the quotation and, hence, the name of this form of typing. Since object boeing has no vehicle_model() method associated with it, an exception is raised .



Example: Write Python Program to Calculate Area and Perimeter of Different Shapes Using Python Polymorphism

import math
class Shape:
    
    def area(self):
        pass
    def perimeter(self):
        pass
class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def area(self):
        print(f"Area of Rectangle is {self.width * self.height}")
    def perimeter(self):
        print(f"Perimeter of Rectangle is {2 * (self.width + self.height)}")
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        print(f"Area of Circle is {math.pi * self.radius ** 2}")
    def perimeter(self):
        print(f"Perimeter of Circle is {2 * math.pi * self.radius}")
    def shape_type(shape_obj):
        shape_obj.area()
        shape_obj.perimeter()
def main():
    rectangle_obj = Rectangle(10, 20)
    circle_obj = Circle(10)
    for each_obj in [rectangle_obj, circle_obj]:
        
        shape_type(each_obj)
if __name__ == "__main__":
    main()
Output
Area of Rectangle is 200
Perimeter of Rectangle is 60
Area of Circle is 314.1592653589793
Perimeter of Circle is 62.83185307179586

In this program, Shape is the base class while Rectangle  and Circle  are the derived classes. All of these classes have common methods area() and perimeter() added to them but their implementation is different as found in each class. Derived classes Rectangle and Circle have their own data attributes. Instance variables rectangle_obj and circle_obj are created for Rectangle and Circle classes respectively. The clearest way to express python Polymorphism

is through the function shape_type() , which takes any object and invokes the methods area() and perimeter() respectively.

Python Operator Overloading and Magic Methods

Operator Overloading is a specific case of python Polymorphism, where different operators have different implementations depending on their arguments. A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names called “Magic Magic Methods for Different Operators and Functions

Python Binary Operators

Operator Method Description

  • add(self, other):

Invoked for Addition Operations

  • sub(self, other):

Invoked for Subtraction Operations

  • mul(self, other):

Invoked for Multiplication Operations

/ truediv(self, other):

Invoked for Division Operations

floordiv(self, other):

Invoked for Floor Division Operations

% mod(self, other) :

Invoked for Modulus Operations

** pow(self, other[, modulo]):

Invoked for Power Operations

<< lshift(self, other) :

Invoked for Left-Shift Operations

> rshift(self, other) :

Invoked for Right-Shift Operations

& and(self, other):

Invoked for Binary AND Operations

^ xor(self, other):

Invoked for Binary Exclusive-OR Operations

| or(self, other):

Invoked for Binary OR Operations


Python Extended Operators

+= _iadd__(self, other):

Invoked for Addition Assignment Operations

-= __isub(self, other) :

Invoked for Subtraction Assignment Operations

*= imul(self, other):

Invoked for Multiplication Assignment Operations

/= idiv(self, other):

Invoked for Division Assignment Operations

//= ifloordiv(self, other) :

Invoked for Floor Division Assignment Operations

%= imod(self, other) :

Invoked for Modulus Assignment Operations

**= __ipow__(self, other[, modulo]):

Invoked for Power Assignment Operations

<<= ilshift(self, other):

Invoked for Left-Shift Assignment Operations

>= irshift(self, other):

Invoked for Right-Shift Assignment Operations

&= iand(self, other) :

Invoked for Binary AND Assignment Operations

classes to define their own behavior with respect to language operators. Python uses\ the word “Magic Methods” because these are special methods that you can define to add magic to your program. These magic methods start with double underscores and end with double underscores. One of the biggest advantages of using Python’s magic methods is that they provide a simple way to make objects behave like built-in types. That means you can avoid ugly, counter-intuitive, and nonstandard ways of using basic operators. The basic rule of operator overloading in Python is, Whenever the meaning of an operator is not obviously clear and undisputed, it should not be overloaded

and always stick to the operator’s well-known semantics.

 You cannot create new operators\ and you can’t change the meaning of operators for built-in types in Python programming language. Consider the standard + (plus) operator. When this operator is used with operands of different standard types, it will have a different meaning. The + operator performs arithmetic addition of two numbers, merges two lists, and concatenates two strings.

^= ixor(self, other) :

Invoked for Binary Exclusive-OR Assignment Operations

|= ior(self, other):

Invoked for Binary OR Assignment Operations

Python Unary Operators

  • neg(self) :

Invoked for Unary Negation Operator

  • pos(self) :

Invoked for Unary Plus Operator

abs() abs():

Invoked for built-in function abs(). Returns absolute

value

~ invert(self):

Invoked for Unary Invert Operator


Python Conversion Operations

complex() complex(self) :

Invoked for built-in complex() function

int() int(self):

Invoked for built-in int() function

long() long(self):

Invoked for built-in long() function

float() float(self) :

Invoked for built-in float() function

oct() oct() :

Invoked for built-in oct() function

hex() hex():

Invoked for built-in hex() function

Python Comparison Operators

< lt(self, other):

Invoked for Less-Than Operations

<= le(self, other) :

Invoked for Less-Than or Equal-To Operations

== eq(self, other) :

Invoked for Equality Operations

!= ne(self, other) :

Invoked for Inequality Operations

= ge(self, other) :

Invoked for Greater Than or Equal-To Operations

gt(self, other):

Invoked for Greater Than Operations


Example: Write Python Program to Create a Class Called as Complex and Implement __add__() Method to Add Two Complex Numbers. Display the Result by Overloading the + Operator

class Complex:
    
    def __init__(self, real, imaginary):
        self.real = real
        self.imaginary = imaginary
    def __add__(self, other):
        return Complex(self.real + other.real, self.imaginary + other.imaginary)
    def __str__(self):
        return f"{self.real} + i{self.imaginary}"
def main():
    complex_number_1 = Complex(4, 5)
    complex_number_2 = Complex(2, 3)
    complex_number_sum = complex_number_1 + complex_number_2
    print(f"Addition of two complex numbers {complex_number_1} and {complex_number_2} is {complex_number_sum}")
if __name__ == "__main__":
    main() 
Output
Addition of two complex numbers 4 + i5 and 2 + i3 is 6 + i8

python polymorphism

Consider the below code having Complex class with real and imaginary as data attributes class Complex:

def init(self, real, imaginary):

self.real = real

self.imaginary = imaginary

Instance variables complex_number_1 and complex_number_2 are created for Complex class.

complex_number_1 = Complex(4, 5)

complex_number_2 = Complex(2, 3)

complex_number_sum = complex_number_1 + complex_number_2

If you try to add the objects complex_number_1 and complex_number_2 then it results in error as shown below.

TypeError: unsupported operand type(s) for +: ‘Complex’ and ‘Complex’

Adding the magic method add__()  within the Complex class resolves this issue. The __add() method takes two objects as arguments with complex_number_1 object

assigned to self and complex_number_2 object assigned to other and it is expected to return the result of the computation. When the expression complex_number_1 + complex_

number_2 is executed, Python will call complex_number_1.__add__(complex_number_2).

def add(self, other):

return Complex(self.real + other.real, self.imaginary + other.imaginary) Thus, by adding this method, suddenly magic has happened and the error, which you received earlier, has gone away. This method returns the Complex object itself by calling the Complex class init() constructor, with self.real + other.real value assigned to real data attribute and self.imaginary + other.imaginary value assigned to the imaginary data attribute.



The add() method definition has your own implementation. This returning object is assigned to complex_number_sum with data attributes real and imaginary. To print the data attributes associated with complex_number_sum object, you have to issue the statements print(complex_number_sum.real) and print(complex_number_sum.imaginary). Instead of issuing the above statements, you can use the object name only within the print statement, for example, print(complex_number_sum) to print its associated data attributes.

This is done by overriding str() magic method. The syntax is str__(self). The __str() method is called by str(object) and the built-in functions format() and print() to compute the “informal,” or nicely printable string representation of an object. The return value must be a string object. The implementation details of str() magic method is shown below

def str(self):

return f”{self.real} + i{self.imaginary}”

The return value of str() method has to be a string, but it can be any string, including one that contains the string representation of integers. In the implementation of the __ str__() magic method, you have customized it for your own purpose. The str() method returns a string with values of real and imaginary data attributes concatenated together, and the character i is prefixed before imaginary data attribute value.


Example: Consider a Rectangle Class and Create Two Rectangle Objects. This Program Should Check Whether the Area of the First Rectangle is Greater than Second by Overloading > Operator

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def __gt__(self, other):
        rectangle_1_area = self.width * self.height

        rectangle_2_area = other.width * other.height
        return rectangle_1_area > rectangle_2_area
def main():
    rectangle_1_obj = Rectangle(5, 10)
    rectangle_2_obj = Rectangle(3, 4)
    if rectangle_1_obj > rectangle_2_obj:
        print("Rectangle 1 is greater than Rectangle 2")
    else:
        print("Rectangle 2 is greater than Rectangle 1")
if __name__ == "__main__":
    main()
 Output
Rectangle 1 is greater than Rectangle 2

python polymorphism

In the above code, rectangle_1_obj _ and rectangle_2_obj are the objects of Rectangle class When the expression if rectangle_1_obj > rectangle_2_obj is executed, the magic method rectangle_1_obj.__gt__(rectangle_2_obj) gets invoked. This magic method calculates the area of two rectangles and returns a Boolean True value if the area of the first rectangle is greater than the area of second rectangle.

 

 

Engr Fahad

My name is Shahzada Fahad and I am an Electrical Engineer. I have been doing Job in UAE as a site engineer in an Electrical Construction Company. Currently, I am running my own YouTube channel "Electronic Clinic", and managing this Website. My Hobbies are * Watching Movies * Music * Martial Arts * Photography * Travelling * Make Sketches and so on...

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button