Introduction

Polymorphism is yet another important aspect of Object Oriented Programming. No Warcraft fans, it doesn't have anything to do with turning people into sheep. That'd be much cooler. Instead, Polymorphism allows methods and functions to use classes with similar functionality in the same way.

Polymorphism cuts down on the amount of code that you need to write by eliminating redundancy in a logical and meaningful way. It relies on you, the programmer, to be clever in your design and harness similarities in objects.

What Is Polymorphism?

Polymorphism is one of those things that's hard to define in a single word or phrase. The term takes slightly different forms in different programming languages, making it a bit harder to pin down concisely. Generally speaking, and in Python, it's the ability of multiple different objects derived from different classes to be used in the same way.

To use the example of cars that this guide has been working with, imagine that you need to write a function that calls the move_forward method on any type of car that it's passed. Every object created from the original "Car" class or any of the subclasses that extend it should have a move_forward method, even if it's been overridden to do something slightly differently in some of the subclasses. This means that through polymorphism, you can write a single function that takes a "Car" object and calls move_forward. Since all of the objects instantiated from the subclasses of "Car" are still technically "Car" objects and have all of its original properties in some form or another, this will work.

Please note that the specification of a single superclass to use as an object type is not entirely necessary in Python. Python is not strongly typed, so you don't have to explicitly define which variable types a method or function takes. In languages like Java, this plays a role in how polymorphism behaves. However, in Python, it's a good idea to think of it along these lines to ensure that the objects that you pass will all have the method or property being used.

Using It In Python

Now, it would really help to see all of this in action. It's not as complex as you're probably thinking right now. First, make sure you're set up with the example classes. If you don't already have them, they're right below.
class Car(object):
    def __init__(self, make = 'Ford', model = 'Pinto', year = '1971', mileage = 253812, color = 'orange'):
        self.__make = make
        self.__model = model
        self.__year = year
        self.__mileage = mileage
        self.__color = color

    def set_make(self, make):
        self.__make = make

    def get_make(self):
        return self.__make

    def set_model(self, model):
        self.__model = model

    def get_model(self):
        return self.__model

    def set_year(self, year):
        self.__year = year

    def get_year(self):
        return self.__year

    def set_mileage(self, mileage):
        self.__mileage = mileage

    def get_mileage(self):
        return self.__mileage

    def set_color(self, color):
        self.__color = color
	def get_color(self):
        return self.__color

    def move_forward(self, speed):
        print("Your %s is moving forward at %s" % (self.__model, speed))

    def move_backward(self, speed):
        print("Moving backward at %s" % speed)


class MuscleCar(Car):
    def __init__(self, make = 'Ford', model = 'Mustang', year = '1965', mileage = 54032, color = 'blue', hp = 325):
		super().__init__(make, model, year, mileage, color)
        self.__hp = hp

    def set_hp(self, hp):
        self.__hp = hp

    def get_hp(self):
        return self.__hp

    def drag_race(self, opponent):
        if (self.__hp > opponent.get_hp()):
            return "You Win!"
        else:
            return "You Lose!"

    def trade_up(self, year, color):
        super().set_year(year)
        super().set_color(color)
        super().set_mileage(0)
With the demo classes set up, you can test out a quick function that uses polymorphism to call move_forward.
newmusclecar = MuscleCar()
newcar = Car()

def go_25(car_object):
	car_object.move_forward("25mph")

go_25(newcar)
go_25(newmusclecar)
Both function calls behave exactly the same, even though they were passed different objects that were created from different classes. Thanks to polymorphism, you don't have to write two different functions and can use the similar properties of the objects to write only one function.

Try overriding one of the move_forward method in the "MuscleCar" class. Run the function again, and see what happens.

class MuscleCar(Car):
	…

	def move_forward(self, speed):
        print("Your awesome %s is roaring down the road  at %s" % (self.__model, speed))

	…

When the function calls the move_forward method on the muscle car object, it still works perfectly, and the overridden version of the method is used.

Closing Thoughts

Polymorphism sounds like it would be a terribly complex concept, but its name is somewhat of a misdirect. The concept itself isn't all that intimidating, and it even helps to simplify the overall structure of your code.

A common theme in Object Oriented Programming is the elimination of redundancy through the reusable and adaptable code. Polymorphism is an important part of that picture.

Exercises

  1. Use the example classes to instantiate a "Car" object and a "MuscleCar" object.
  2. Create a function that can interact with either type of "Car" object.
  3. Call your function twice, passing it both objects.
  4. Override one of the methods in the MuscleCar class. Be sure not to change the parameters that it accepts to drastically.
  5. Call your function again.
  6. Create a new class that inherits from the "Car" class.
  7. Instantiate a new object from your newly created class. Pass the object to your function.