Polymorphism In Python: First Step To Build Systems Like Chat Gpt

Have you heard of ChatGPT🤖? (We will understand polymorphism with its help.)

Must have.

“ChatGPT is a large language model developed by OpenAI, designed to generate human-like responses to text-based queries.”

This is written by ChatGPT(The line above). You can ask it anything and it will answer. Much like Google but in a conversational tone.

It would be best if you tried it once.

Anyways OpenAI hasn’t paid 💵 me to promote it.😢

I’ve mentioned it because it relates to polymorphism…

Previous post’s challenge’s solution

class Account:
    def __init__(self):
        # Constructor for the Account class, prints a welcome message when object is created
        print("Welcome To our Bank!")
        print("Here you can:")
        print("Open and A/C, \ndeposit and withdraw cash in it, \nand can get your A/C statement.")
    def open_account(self, name, amount, password):
        # Method to open a new account and initialize instance variables
        self.name = name
        self.amount = amount
        self.password = password
        print(f"\nThank you {self.name} for opening a account in our bank.")
        print(f"You have ${self.amount} as an initial amount in your account.\n")
 
    def deposit(self, amt, password):
        # Method to deposit money into the account
        # Check if the password is correct or not.
        if self.password != password:
            print("Incorrect password!")
            return None
        # Check if the amount is positive or not.    
        if amt<0:
            print("Can't deposit a negative amount!")
            return None
        # If all checks passed increase the amount.
        self.amount += amt
        print(f"New balance is ${self.amount}.")
        return self.amount
    
    def withdraw(self, amt, password):
        # Method to withdraw money from the account
        # Check if the password is correct or not.
        if self.password != password:
            print("Incorrect password!")
            return None
        # Check if the amount is positive or not.    
        if amt<0:
            print("Can't withdraw a negative amount!")
            return None
        # Check whether the amount is present in the account or not.
        if amt>self.amount:
            print("Insufficient funds!")
            return None
        # If all checks passed decrease the amount.
        self.amount -= amt
        print(f"New balance is ${self.amount}.")
        return self.amount
    def show_amount(self, password):
        # Method to show the account details if password is correct
        if password != self.password:
            print("Incorrect password!")
            return None
        return f"Account Holder's name {self.name}\n amount = ${self.amount}"   
    
# Create an instance of the Account class
oacc = Account()
# Get user input to open a new account
n = input("Enter your name: ")
a = int(input("Enter initial amount: "))
p = input("Enter a strong password: ")
oacc.open_account(n, a, p)
#depositing money in the bank
oacc.deposit(10, "abc")
#withdrawing money from the bank
oacc.withdraw(5, "abc")
#showing the amount of money in the bank
print(oacc.show_amount("abc"))

Read the code to understand the answer. It is well documented.👍

If you still have any doubts 🤔 ask in the comment section below.

What is Polymorphism?

Polymorphism means many 🤹 forms of a single☝️ thing. It is derived from a Greek word:

poly = “much” or “many,”

morphism = “shape,” “form,” or “structure.”

ChatGPT🤖 is an example of it. As this one AI model can be your guide, chef👨‍🍳, counselor👩‍⚕️, teacher👩‍🏫, doubt solver💡, coder👨‍💻, and much more.

Polymorphism is about the same method name being used by multiple classes.

Let’s see some coded examples of it to be more clear about it.

Examples in standard python

The ‘+’ Operator

The + operator can be used to add two✌️ numbers:

print(10+10)
#20

It can also be used to concatenate two✌️ strings🧵:

print('10'+'10')
#1010

This means this __add__() method (it is a magic method) is used differently in the int class and str class.

Same method name in multiple classes.

The len() Function

The len() function when used on a string🧵 gives the length of the string:

print(len("name"))
#4

When used on a list📝 gives the no. of elements in a list:

print(len([1,2,32,87,97]))
#5

And when used on a dictionary📖 gives no. of keys in it:

print(len({1: "One", 2: "Two", 3: "Three"}))
#3

Same method name in multiple classes.

The grid() and pack() Functions

The grid and pack 🧳 functions from the CustomTkinter library can be used for all kinds of widgets:

from customtkinter import *
app = CTk()
btn = CTkButton(master=app, text="Click me")
btn.pack()
t = CTkLabel(master=app, text="I am a label")
t.pack()
app.mainloop()

The grid function’s example:

from customtkinter import *
app = CTk()
btn = CTkButton(master=app, text="Click me")
btn.grid()
t = CTkLabel(master=app, text="I am a label")
t.grid()
app.mainloop()

So, now you must be quite clear 💡 about the concept of polymorphism.

But, you might also be wondering🤔, why on the earth🌎 we need this concept?

Why is it so popular?

Advantages of polymorphism

No need to write the same code again and again

Imagine🤩 you are making a game🎮 related to attacking and fighting.🔫

Now, all games have some levels⛳ or benchmarks.

When one reaches the benchmark one can get the character⛹️‍♀️ upgraded🌟 in your game.

Let’s write classes for the characters:

from abc import ABC, abstractmethod
class Character(ABC):
    def __init__(self):
        self.name = ""
        self.damage = 0
    
    @abstractmethod
    def shoot(self):
        pass
    
class Player(Character):
    def __init__(self):
        super().__init__()
        self.name = "Player"
        self.damage = 10
        
    def shoot(self):
        print(f"{self.name} shoots and gives a level {self.damage} damage to the enemy!")
        
class Head(Character):
    def __init__(self):
        self.name = "Head"
        self.damage = 50
        
    def shoot(self):
        print(f"\n{self.name} shoots and gives a level {self.damage} damage to the enemy!")
        print("The holder gets 2 bullets extra\n")
        
class Boss(Character):
    def __init__(self):
        self.name = "Boss"
        self.damage = 1000
        
    def shoot(self):
        print(f"{self.name} shoots and gives a level {self.damage} damage to the enemy!")
        print("Boss gets infinite bulllets")
player = Player()
player2 = Head()
player3 = Boss()
player.shoot() 
player2.shoot() 
player3.shoot()

Output:

Player shoots and gives a level 10 damage to the enemy!
Head shoots and gives a level 50 damage to the enemy!  
The holder gets 2 bullets extra
Boss shoots and gives a level 1000 damage to the enemy!
Boss gets infinite bulllets

So, we have defined a Character⛹️‍♀️ class and three subclasses (Player, Head, Boss) that inherit from it.

Each subclass has a “shoot”🔫 method that prints a different message based on the damage🔥 that the level of character can have.

The Character class has an abstract shoot method that must be overridden by the subclasses.

Here we did not need to write different methods for each class they used the same “shoot” method for the character’s shooting behavior.

And you can reuse this method if you feel like adding some new characters.

Make it big, tension free

Now, to manage this game🎮 you would need some code in the main section:

while True:
    print("Welcome to the game!")
    print("Select your level:")
    print("1. Player")
    print("2. Head")
    print("3. Boss")
    print("4. Exit")
    c = int(input("Enter your level: "))
    if(c==1):
        player = Player()
    elif(c==2):
        player = Head()
    elif(c==3):
        player = Boss()
    elif(c==4):
        break
    else:
        print("No such level!") 
        continue
    player.shoot()

Now if you want to add one more level⛳ all you need to do is make its class and add an elif statement along with a choice.

You can do this because you are using the same method name in all the classes. You can add as many characters🧛 as you want.

Thus, polymorphism provides code extensibility.

Make the tech easy to use

Finally, imagine🤩 you had different methods to measure the length of sequences in python.

len() for a list, length() for a tuple, alen() for a dictionary, and so on.

Would it not be quite more complicated😵 to work with these methods than it is now?

Let’s say you manage👍 this much, what about other functionalities they too will have different names for different sequences.

Polymorphism makes your code easier and more comfortable to deal with.

Thus, it simplifies your code. (In future tech)

Let’s make a polymorphic function…

Too much about polymorphism—what is it, examples, and advantages? Let’s get something into our code.

We will calculate the area of different shapes.🟢🟦🔺

The shapes are:

  • Sir Round-a-lot: π*r*r.🟢
  • Mr. Right-Angle: l*b.🟦
  • The Three-point Wonder: 0.5*b*h.🔺

Let’s make:

def area(shape, *d):
    if shape == "round":
        return 3.14 * d[0] ** 2
    elif shape == "right_angle":
        return d[0] * d[1]
    elif shape == "wonder":
        return 0.5 * d[0] * d[1]
    else:
        return None
print(area("round", 2)) # prints 12.56
print(area("right_angle", 2, 3)) # prints 6
print(area("wonder", 4, 6)) # prints 12.0

For those who don’t know🙋 Mr. d (*d to be specific) click here.

Here, we use the same method name for calculating areas of a variety of shapes. You add more if you are a shape lover.🧡

In this, we achieved polymorphism using the conditional statement if. You can use the match statement too.

Polymorphism in OOP

There are 4 4️⃣ major ways to implement polymorphism in python OOP or in general in any language:

  • Method Overriding (Related To Inheritance)
  • Method overloading
  • Operator Overloading(Magic Methods)
  • Duck typing

Actually, there are 5🖐️, as there’s something called Operator Overriding.

Method Overriding(Polymorphism and Inheritance)

Imagine you and your sibling🧑‍🤝‍🧑 were sitting in your rooms. Your parents weren’t home for a week.

You can imagine the condition of your rooms. It’s completely devasted🗑️ according to your mom.👿

Now, she gives you a punishment to do the cleaning🧹 job at your aunt’s👩 house for a week.

You both know that the aunt would reward🤑 you both if you both are able to do the cleaning job better✨ than normal.

On top of basic cleaning, you guys did something extra✨. And bingo you got the game👾 you both wished to have for a long.

This is method overriding. Yes!

In programming, it’s when a subclass decides to clean up(It can be any task) with its method better than its superclass’s method, by providing its own implementation of the method, it is called method overriding.

Let’s see the code to be clearer.

class Room:
    def clean(self):
        print("Cleaning the room the old-fashioned way...")
class CoolRoom(Room):
    def clean(self):
        print("Cleaning the room in a cooler, more efficient way!")
# Create instances of the classes
room = Room()
cool_room = CoolRoom()
# Call the clean method on both objects
print("Cleaning the room...")
room.clean()
print("Cleaning the cool room...")
cool_room.clean()

Here we used single☝️ inheritance.

Multiple and Multilevel inheritances can also be used just as fine.👍

In terms of definition:

"Method overriding is a feature of object-oriented programming where a subclass provides its own implementation of a method that is already defined in its superclass"

By the way, it is also called run-time polymorphism.

Method overloading

It is also called compile-time polymorphism.

It says having the same method names with different parameters.

Something like this:

def abilities(a1, a2):
   return f"{a1} and {a2}"
def abilities(a1, a2, a3):
   return f"{a1}, {a2}, and {a3}"
print(abilities("crushing eggs with head", "gorilla dance", "Loudest burp"))

Output:

crushing eggs with head, gorilla dance, and Loudest burp

Here’s a problem (I am not talking about the abilities they are real, except for the gorilla dance) python doesn’t support it this way.

What I mean is that we cannot use the first function. We need to give 3 arguments.

However, there is a way:

def abilities(a1, *a):
   print(a1, end=" ")
   for i in a:
      print(i, end=" ")
abilities("crushing eggs with head", "gorilla dance", "Loudest burp")

Output:

crushing eggs with head, gorilla dance, and Loudest burp

If you by any chance do not remember Mr. a then, read the function’s post.

Operator Overloading(Magic Methods)

Operator overloading is like giving the built-in operators (you know, the plus sign, the minus sign, and their friends) some superpowers.

It’s like upgrading them from regular old calculators to fancy gadgets that can do more!

We have +,-,*,/,etc in python. Right?

These operators are implemented as methods in python.

__add__() for +

__sub__() for -

__mul__() for *

These methods are called magic methods in python.

class Number:
    def __init__(self, value):
        self.value = value
    
    # adding two objects
    def __add__(self, o):
        if type(o) == int or type(o) == Number or type(o) == float:
            return self.value + o.value
        
n = Number(10)
n1 = Number(10)
result = n + n1
print(result)

Here, we have modified our __add__() method to only add if the number is an integer, float, or a Number type of variable.

List of majorly used Magic/dunder method

MethodDescription
__init__(self[, args...])Constructor method that initializes an instance of a class
__str__(self)Returns a string representation of an object
__repr__(self)Returns a string representation of an object that can be used to recreate it
__len__(self)Returns the length of an object
__getitem__(self, key)Returns the value of an element at a given key/index
__setitem__(self, key, value)Sets the value of an element at a given key/index
__delitem__(self, key)Deletes an element at a given key/index
__iter__(self)Returns an iterator object for iterating over an object
__next__(self)Returns the next item in an iterator object
__eq__(self, other)Checks if two objects are equal
__lt__(self, other)Checks if an object is less than another object
__gt__(self, other)Checks if an object is greater than another object
__le__(self, other)Checks if an object is less than or equal to another object
__ge__(self, other)Checks if an object is greater than or equal to another object
__add__(self, other)Performs addition with another object
__sub__(self, other)Performs subtraction with another object
__mul__(self, other)Performs multiplication with another object
__truediv__(self, other)Performs true division with another object
__floordiv__(self, other)Performs floor division with another object
__mod__(self, other)Performs modulus division with another object
__pow__(self, other)Performs exponentiation with another object
__and__(self, other)Performs bitwise and with another object
__or__(self, other)Performs bitwise or with another object
__xor__(self, other)Performs bitwise xor with another object
__invert__(self)Performs bitwise inversion of an object
__call__(self, args)Allows an object to be called as a function
__getattr__(self, name)Gets an attribute of an object that does not exist
__setattr__(self, name, value)Sets an attribute of an object
__delattr__(self, name)Deletes an attribute of an object
__dir__(self)Returns a list of valid attributes for an object
Note that this is not an exhaustive list of all magic methods in Python, but rather a selection of commonly used ones.

Operator Overriding

This is not a commonly used term in python. It’s usually referred to as redefining.

Instead of modifying it redefines a method.

class String:
    def __init__(self, text):
        self.text = text
    
    # adding two objects
    def __add__(self, o):
        return f"Do you really think I can print sum of {self.text} and {o.text}"
        
s = String("Hello")
s1 = String("Hi")
result = s + s1
print(result)

Here, we changed what the __add__() method did adding or concatenating two values. We returned a string instead.

This is operator overriding.

Duck typing

Ever wonder why you don’t need to declare the datatype of a variable in python like you need to do in C?

It is because of Duck Typing.

Duck typing is giving priority to behavior(len, +) over type(int, str, list). (Behavior means the method that class aka datatype has)

Python dynamically determines the type of variable based on the value it holds, so you don’t need to explicitly declare the data type of a variable in Python

Here’s an example of duck typing:

We will make 3 classes CutieCat, DazzlingDog, and CuddlyCrocodile. Now, all of these animals can be trained.

So, Let’s have a train method in all these classes:

class CutieCat:
    def train(self):
        print("Training a cat is best accomplished through positive reinforcement and repetition of desired behaviors, such as using treats and clicker training.\n")
class DazzlingDog:
    def train(self):
        print("Reward good behavior and repeat desired actions consistently.\n")
class CuddlyCrocodile:
    def train(self):
        print("Teaching a cartoon crocodile tricks is as easy as getting a rabbit to wear a tuxedo.\n")
#creating an instance of all three
c = CutieCat()
d = DazzlingDog()
cr = CuddlyCrocodile()
#storing that in a list
a = [c,d,cr]
#calling the train method on all of them.
for i in a:
    i.train()

Output:

Training a cat is best accomplished through positive reinforcement and repetition of desired behaviors, such as using treats and clicker training.
Reward good behavior and repeat desired actions consistently.
Teaching a cartoon crocodile tricks is as easy as getting a rabbit to wear a tuxedo.

Even though a cat, a dog, and a crocodile are worlds apart they are considered as same because they have the same method.

Similarly, a string, a list, and a dictionary are completely different, they are considered the same by the len() function as they all have a __len__() method.

Hope you are clear with this concept.

Conclusion

In this post, we saw what is polymorphism with the example of ChatGPT.

We saw some examples of where polymorphism is used by python.

We saw why do we need it with an example of a simple video game.

Finally, we saw how to implement polymorphism ourselves in OOP in 5 different ways.

I hope you are clear with polymorphism now. If you still have any doubts then you can refer to the Revision section below or can ask in the comment section below.

Here we come to an end of this post and to the end of OOP concepts in python.

We will see advanced python topics from the next post.

Challenge 🧗‍♀️

Challenge time!

Make a video game. Not a complicated one of course.

Now, you have: (Read them once it will be fun)

  • Unicorn: Has the power to grant wishes, but only ones that involve glitter and rainbows.
  • Mermaid: Has the ability to breathe underwater, but also has an insatiable craving for sushi.
  • Dragon: Can breathe fire, but also has a fear of heights.
  • Yeti: Has the power of invisibility, but only when it’s snowing and there are no other Yetis around to watch.
  • Loch: Has the power to summon a lake monster tsunami, but can only use it once every 1000 years.
  • Minotaur: Can teleport anywhere in the world, but always ends up facing the wrong direction when it arrives.
  • Sphinx: Has the power of telekinesis, but can only move things that are lighter than a feather.
  • Kraken: Has the power to control the ocean waves, but gets seasick easily.
  • Phoenix: Has the ability to rise from the ashes, but has a terrible memory and often forgets where it left its keys.

A list of imaginary mythical creatures.

You need to choose any three creatures(can go for more).

Use polymorphism to give them abilities. (Class sub-classes refer to this example)

The main code should allow seeing the ability of a particular creature when its name is clicked. (radio buttons would work here)

You are meant to make it using a GUI(only if have read all the GUI posts).

Revision

Which is faster overloading or overriding in python?

In other languages, there is a specific answer. But, for python, it depends on the use case, both can be efficient in their own ways. So, there’s no one-size-fits-all answer to this question.

What is the difference between Operator Overloading and Operator Overriding?

Operator overloading is when you add functionality to the built-in operators by defining their behavior for custom classes. Operator overriding is when you redefine the behavior of built-in operators for the existing classes.

What is the difference between Method Overloading and Method Overriding?

Method overloading is the same method with different no. of parameters. Method overriding is when a subclass provides its own implementation of a method that is already present in its parent class.

What is the difference between Operator Overloading and Method Overloading?

Operator overloading is when you add functionality to the built-in operators by defining their behavior for custom classes. Method overloading is the same method with different no. of parameters.

What is the difference between Method Overriding and Operator Overriding?

Method overriding is when a subclass provides its own implementation of a method that is already present in its parent class. Operator overriding is when you redefine the behavior of built-in operators for the existing classes.

What is Runtime polymorphism in Python?

Runtime polymorphism is another name for method overriding. It is when a subclass provides its own implementation of a method that is already present in its parent class.

What is Compile time polymorphism in Python?

Compile time polymorphism is another name method overloading. It is the same method with different no. of parameters.

List of all Magic/Dunder methods in python

Here’s an exhaustive list of majorly used Magic/dunder methods in python

Leave a Reply