Back to: Python Programming
Polymorphism, in the context of Python and object-oriented programming, refers to the ability of an object to take on many forms (from the Greek poly meaning “many” and morphe meaning “form”. This means that a single interface (like a function or method name) can be used to represent different underlying types or classes, and the specific behavior will depend on the object’s type.
Key aspects of polymorphism in Python:
Duck Typing: Python primarily achieves polymorphism through duck typing. This principle states, “If it walks like a duck and quacks like a duck, then it’s a duck.” In Python, if an object has the required methods or attributes, it can be treated as if it belongs to a certain type, regardless of its actual class hierarchy.
class Duck:
def speak(self):
print("Quack!")
class Dog:
def speak(self):
print("Woof!")
def make_sound(animal):
animal.speak()
duck_instance = Duck()
dog_instance = Dog()
make_sound(duck_instance) # Output: Quack!
make_sound(dog_instance) # Output: Woof!
In this example, make_sound works with both Duck and Dog objects because both classes have a speak() method.
Inheritance and Method Overriding: Polymorphism can also be achieved through inheritance, where child classes inherit from a parent class and then override methods to provide their specific implementations.
class Animal:
def speak(self):
raise NotImplementedError("Subclasses must implement this method")
class Cat(Animal):
def speak(self):
return "Meow!"
class Dog(Animal):
def speak(self):
return "Woof!"
animals = [Cat(), Dog()]
for animal in animals:
print(animal.speak())
Benefits of Polymorphism:
- Code Reusability: Write more generic functions and classes that can work with various types of objects.
- Flexibility and Extensibility: Easily add new classes without modifying existing code that uses polymorphic interfaces.
- Improved Readability and Maintainability: Reduces the need for complex conditional logic (e.g.,
if-elsechains) to handle different object types.
Example
In the following example we will make a class of Shape, then a class of Circle, which will inherit from Shape, and so on for classes Square and Triangle.
class Circle(Shape): # the class of Circle will inherit from Shape
pass
class Square(Shape): # the class of Square will inherit from Shape
pass
class Triangle(Shape): # the class of Triangle will inherit from Shape
pass
circle = Circle() # create a circle object, it identifies as a circle and a shape
square = Square() # create a square object, it identifies as a square and a shape
triangle = Triangle() # create a triangle object, it identifies as a triangle and a shape
# If we want to create a list of shapes, they have in common being all Shapes.
shapes = [Circle(), Square(), Triangle()]
Filling in some of the classes so we can calculate the area of the shapes – we’ll use the abstract method.
The @abstractmethod decorator in Python is used to declare an abstract method within an abstract base class (ABC). Abstract methods are those that are declared but do not contain an implementation in the base class; instead, they are intended to be implemented by concrete subclasses.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass # Or raise NotImplementedError
class Circle(Shape):
def __init__(self, radius): # define a constructor passing in radius
self.radius = radius
def area(self): # define area method for Circle class
return 3.14 * self.radius ** 2
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height * 0.5
shapes = [Circle(4), Square(5), Triangle(6, 7)] # pass in some arguments, 4 for Circle...
for shape in shapes: # loop to iterate through our shapes
print(f"{shape.area()}cm²") # To add squared superscript - Alt + 0178
What if we were to add a class that were completely unrelated to shapes? For example:
class Pizza(Circle): # we need the Pizza class to inherit from the Circle class
def __init__(self, topping, radius):
super().__init__(radius) # within Circle class we are already assigning the radius
self.topping = topping # so we can call the super() constructor
The complete program:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height * 0.5
class Pizza(Circle):
def __init__(self, topping, radius):
super().__init__(radius)
self.topping = topping
shapes = [Circle(4), Square(5), Triangle(6, 7), Pizza("pepperoni", 15)]
for shape in shapes:
print(f"{shape.area()}cm²")