Python Object-Oriented Programming: Polymorphism

Let me tell you a story about polymorphism in Python for object-oriented programming. It’s like having multiple personalities in one program but without all the drama and therapy bills. Polymorphism is when an object can take on different forms or behaviors depending on its context. In other words, it allows us to write code that works with a variety of objects without knowing exactly what type they are at compile time. This makes our programs more flexible and easier to maintain over time.

In Python, we have two types of polymorphism: method overloading (not supported) and dynamic dispatching (supported). Let’s take a look at an example using the latter.

Let’s say we have a base class called Animal that has a common behavior for all animals making noise. We can define this in our code like so:

# Defining a base class called Animal
class Animal:
    # Initializing the class with a name attribute
    def __init__(self, name):
        self.name = name
    
    # Defining a method for making noise
    def make_noise(self):
        # Printing a generic noise for all animals
        print("I'm an animal and I make a generic noise!")
        
    # ... other methods for the base class... (not shown in this example)

Now let’s create some derived classes that inherit from Animal, but have their own unique behaviors. For example:

# Creating a derived class Dog that inherits from the Animal class
class Dog(Animal):
    # Defining the constructor method with the required parameter 'name'
    def __init__(self, name):
        # Calling the constructor of the parent class using the super() function
        super().__init__(name)
    
    # Defining a method specific to the Dog class to make noise
    def make_noise(self):
        # Using string formatting to print the name of the dog
        print("Woof! My name is {}.".format(self.name))
        
# ... other methods for the Dog class...

In this example, we’re overriding (or redefining) the `make_noise()` method in our derived class to add some specific behavior that’s unique to dogs. But what if we have another animal that also makes a noise? Let’s say it’s a cat:

# Defining a class called Cat that inherits from the Animal class
class Cat(Animal):
    # Defining the constructor method for the Cat class, which takes in a name parameter
    def __init__(self, name):
        # Calling the constructor method of the parent class (Animal) and passing in the name parameter
        super().__init__(name)
    
    # Defining the make_noise method for the Cat class
    def make_noise(self):
        # Printing a string with the cat's name using string formatting
        print("Meow! My name is {}.".format(self.name))
        
# ... other methods for the Cat class...

Now let’s say we have a function that takes an Animal object and calls its `make_noise()` method:

# Function that takes a list of Animal objects and calls their `make_noise()` method
def make_animal_noises(animals):
    # Loop through each animal in the list
    for animal in animals:
        # Call the `make_noise()` method of the current animal
        animal.make_noise()
        
# ... other code...

When we call this function with a list of Animal objects, it will automatically use the correct `make_noise()` method based on the type of object passed to it whether it’s a Dog or Cat (or any other derived class that inherits from Animal). This is called dynamic dispatching.

Polymorphism in Python for object-oriented programming can help make your code more flexible and easier to maintain over time, without all the drama and therapy bills.

SICORPS