Python - Polymorphism: A Beginner's Guide

Hello there, future Python maestros! Today, we're going to embark on an exciting journey into the world of polymorphism in Python. Don't worry if that word sounds like a spell from Harry Potter - by the end of this tutorial, you'll be wielding polymorphism like a pro!

Python - Polymorphism

What is Polymorphism in Python?

Imagine you have a magic wand (bear with me, we're going full Hogwarts here). This wand can transform into different objects - sometimes it's a pen, sometimes it's a sword, and sometimes it's a flashlight. That's essentially what polymorphism is in programming!

In Python, polymorphism allows objects of different classes to be treated as objects of a common base class. It's like having a Swiss Army knife of code - one interface, many implementations.

Let's look at a simple example:

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def animal_sound(animal):
    print(animal.speak())

# Creating objects
dog = Dog()
cat = Cat()

# Using polymorphism
animal_sound(dog)  # Output: Woof!
animal_sound(cat)  # Output: Meow!

In this example, both Dog and Cat are derived from the Animal class. The animal_sound function doesn't care what type of animal it receives - it just calls the speak method. This is polymorphism in action!

Ways of Implementing Polymorphism in Python

Python offers several ways to implement polymorphism. Let's explore them one by one:

Duck Typing in Python

Duck typing is a concept in Python that focuses on the behavior of an object rather than its type. As the saying goes, "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."

Here's an example:

class Duck:
    def quack(self):
        print("Quack, quack!")

class Person:
    def quack(self):
        print("I'm pretending to be a duck!")

def make_it_quack(thing):
    thing.quack()

# Creating objects
duck = Duck()
person = Person()

# Using duck typing
make_it_quack(duck)    # Output: Quack, quack!
make_it_quack(person)  # Output: I'm pretending to be a duck!

In this example, make_it_quack doesn't care about the type of the object it receives. As long as the object has a quack method, it will work.

Method Overriding in Python

Method overriding is when a derived class provides a specific implementation for a method that is already defined in its base class. It's like telling your parent, "I know you do it this way, but I'm going to do it my way!"

Here's an example:

class Vehicle:
    def move(self):
        print("I'm moving!")

class Car(Vehicle):
    def move(self):
        print("I'm driving on the road!")

class Boat(Vehicle):
    def move(self):
        print("I'm sailing on the water!")

# Creating objects
vehicle = Vehicle()
car = Car()
boat = Boat()

# Using method overriding
vehicle.move()  # Output: I'm moving!
car.move()      # Output: I'm driving on the road!
boat.move()     # Output: I'm sailing on the water!

In this example, both Car and Boat override the move method of the Vehicle class with their own specific implementations.

Overloading Operators in Python

Python allows you to define how operators behave when applied to objects of your custom classes. This is called operator overloading.

Here's an example:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f"({self.x}, {self.y})"

# Creating objects
p1 = Point(1, 2)
p2 = Point(3, 4)

# Using operator overloading
p3 = p1 + p2
print(p3)  # Output: (4, 6)

In this example, we've overloaded the + operator for our Point class by defining the __add__ method.

Method Overloading in Python

Unlike some other languages, Python doesn't support method overloading in the traditional sense. However, we can achieve similar functionality using default arguments or variable-length arguments.

Here's an example:

class Calculator:
    def add(self, *args):
        return sum(args)

# Creating an object
calc = Calculator()

# Using method overloading-like functionality
print(calc.add(1, 2))        # Output: 3
print(calc.add(1, 2, 3))     # Output: 6
print(calc.add(1, 2, 3, 4))  # Output: 10

In this example, our add method can take any number of arguments, simulating method overloading.

Polymorphism Methods Table

Here's a table summarizing the polymorphism methods we've discussed:

Method Description Example
Duck Typing Focuses on the behavior of an object rather than its type make_it_quack(thing)
Method Overriding Derived class provides specific implementation for a method defined in its base class Car.move() overrides Vehicle.move()
Operator Overloading Defining how operators behave for custom classes Overloading + for Point class
Method Overloading-like Using default or variable-length arguments to simulate method overloading Calculator.add(*args)

And there you have it, folks! You've just taken your first steps into the wonderful world of polymorphism in Python. Remember, practice makes perfect, so don't be afraid to experiment with these concepts in your own code. Before you know it, you'll be shaping your code like a master sculptor, creating elegant and flexible programs that would make even Michelangelo jealous!

Happy coding, and may the polymorphism be with you!

Credits: Image by storyset