Understanding Inheritance and Polymorphism in Python: Mastering Object-Oriented Programming (Part 2)

NIBEDITA (NS)
8 min readOct 30, 2023

Object-Oriented Programming (OOP) is a powerful paradigm that enables developers to model real-world scenarios in a structured and efficient way. Python is a versatile and popular programming language, that fully supports OOP concepts. Two crucial concepts in OOP are Inheritance and Polymorphism, which enable developers to create efficient and maintainable code. In this article, we will understand these concepts and know what’s their use in Python. I’ll provide clear code examples to illustrate their application and try explaining them in simple terms.

Understanding Inheritance and Polymorphism in Python: Mastering Object-Oriented Programming (Part 2)

Before jumping into inheritance and polymorphism, let’s start by understanding the basics of Object-Oriented Programming. OOP is built around the idea of modeling the real-world using objects and classes.

We’ve already discussed about Objects and Classes in the previous article. If you didn’t yet explore its Part 1, then I’ll suggest going and try to understand the concepts and code first, then jump into this article so that you’ll have a clear understanding of these concepts too.

Or, if you want to see the whole Python Mastery series from the beginning:

To get a quick recap, Objects are instances of classes and represent real-world entities. For example, if you’re building a program to manage a zoo, you might have classes like Animal and Cage, with objects like lion, elephant, and safariCage. Classes are blueprints for objects. They define the structure and behavior of objects. In Python, you can define a class using the class keyword.

Alright, let’s jump into our main concept now, which is Inheritance and Polymorphism.

Inheritance

Inheritance is a fundamental concept in OOP that allows us to create a new class based on an existing class. The new class inherits attributes and behaviors from the existing class, and it can also add its unique attributes and behaviors. This promotes code reuse and allows for building a hierarchy of classes, which is particularly useful when modeling objects that share common characteristics.

class Animal:
def __init__(self, name, species):
self.name = name
self.species = species

def speak(self):
pass

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

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

In this example, we have an Animal class with a constructor that takes a name and species parameter and a speak method. The speak method is not implemented in the Animal class but is left as an abstract method with the pass keyword. This means that any class inheriting from Animal is required to implement the speak method.

We then have two subclasses, Dog and Cat, which inherit from the Animal class. Each of these subclasses provides its implementation of the speak method, allowing them to make their respective sounds. This is a classic example of how inheritance works.

Inheritance promotes code reuse and simplifies the design of your program by allowing you to create specialized classes that inherit common attributes and behaviors from a base class. This makes your code more maintainable and reduces redundancy.

Polymorphism

Polymorphism is another essential OOP concept that allows objects of different classes to be treated as objects of a common base class. It enables us to write code that can work with objects without knowing their exact types.

Polymorphism is achieved through method overriding and method overloading. Method overriding is the practice of defining a method in a subclass that already exists in the parent class. Method overloading, on the other hand, is the ability to define multiple methods in the same class with the same name but different parameters.

class Animal:
def __init__(self, name, species):
self.name = name
self.species = species

def speak(self):
pass

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

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

In this example, both the Dog and Cat classes override the speak method inherited from the Animal class. The speak method is redefined in each subclass to provide a specific implementation.

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

dog = Dog("Buddy", "Canine")
cat = Cat("Whiskers", "Feline")

make_animal_speak(dog) # Output: Woof!
make_animal_speak(cat) # Output: Meow!

The make_animal_speak function accepts an Animal object as its argument. Since both Dog and Cat are subclasses of Animal and have overridden the speak method, we can pass instances of both Dog and Cat to this function, and it will call the appropriate speak method for each, demonstrating polymorphism.

Polymorphism is a powerful tool that promotes flexibility and extensibility in our code. It allows us to write functions and methods that can work with a variety of objects without needing to know their specific types.

Inheritance and Polymorphism in Real-World Scenarios

Now that we’ve covered the theoretical aspects of inheritance and polymorphism, let’s have a look at some real-world scenarios in which these concepts are essential.

1. GUI Frameworks:

Graphical User Interface (GUI) frameworks often employ inheritance and polymorphism extensively. In GUI development, we typically have a hierarchy of classes representing different UI elements. For example, we might have a base class UIElement, which could have subclasses like Button, TextBox, and Label.

class UIElement:
def render(self):
pass

class Button(UIElement):
def render(self):
# Render button specific code
pass

class TextBox(UIElement):
def render(self):
# Render text box specific code
pass

By using inheritance, we can ensure that all UI elements share common attributes and methods. Polymorphism allows us to treat all these elements as instances of the base class UIElement. This makes it easier to write code that can handle a wide range of UI elements without needing to know their specific types.

2. Database Models:

In web development, database models often follow an inheritance hierarchy. Let’s consider a scenario where we have a base class User and subclasses like Admin, Customer, and Employee.

class User:
def __init__(self, username, email):
self.username = username
self.email = email

class Admin(User):
def promote_employee(self, employee):
# Admin-specific functionality
pass

class Customer(User):
def make_purchase(self, product):
# Customer-specific functionality
pass

class Employee(User):
def calculate_salary(self):
# Employee-specific functionality
pass

In this case, each subclass inherits the attributes and methods from the User class, and they can also have their unique methods. This promotes code reuse while allowing for specialization.

Polymorphism can come into play when we want to perform actions on users but don’t necessarily know whether they are admins, customers, or employees. By treating all users as instances of the User class, we can write code that is more flexible and versatile.

3. Drawing Applications:

In a drawing application, we can use inheritance and polymorphism to create a variety of shapes. Consider a base class Shape with subclasses like Circle, Rectangle, and Triangle.

class Shape:
def draw(self):
pass

class Circle(Shape):
def draw(self):
# Draw a circle
pass

class Rectangle(Shape):
def draw(self):
# Draw a rectangle
pass

class Triangle(Shape):
def draw(self):
# Draw a triangle
pass

Using inheritance, we can define a common interface for all shapes, and each shape subclass provides its implementation of the draw method. When we want to render a complex drawing with various shapes, we can iterate through a list of shapes and call the draw method on each shape, irrespective of its specific type. This is a prime example of polymorphism in action.

Practical Examples with Code

Example 1: Shapes and Areas

In this example, we’ll create a hierarchy of shapes and calculate their areas using inheritance and polymorphism.

import math

class Shape:
def area(self):
pass

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return math.pi * self.radius ** 2

class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height

def area(self):
return 0.5 * self.base * self.height

# Calculate areas
circle = Circle(5)
rectangle = Rectangle(4, 6)
triangle = Triangle(3, 4)

shapes = [circle, rectangle, triangle]

for shape in shapes:
print(f"Area of {type(shape).__name__}: {shape.area()}")

In this example, we have a base class Shape with an area method. Three subclasses, Circle, Rectangle, and Triangle, inherit from Shape and provide their implementations of the area method. We calculate the areas of different shapes using a list of shape objects and polymorphism.

Example 2: File Processing

Suppose you’re working on a file processing application where you need to read and write different types of files. In this scenario, you can use inheritance to create a common interface for file types, and polymorphism allows you to process these files uniformly.

class File:
def __init__(self, filename):
self.filename = filename

def read(self):
pass

def write(self, data):
pass

class TextFile(File):
def read(self):
with open(self.filename, 'r') as file:
return file.read()

def write(self, data):
with open(self.filename, 'w') as file:
file.write(data)

class BinaryFile(File):
def read(self):
with open(self.filename, 'rb') as file:
return file.read()

def write(self, data):
with open(self.filename, 'wb') as file:
file.write(data)

# Example usage
text_file = TextFile('example.txt')
binary_file = BinaryFile('example.bin')

text_file.write('Hello, World!')
binary_file.write(b'\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21')

text_content = text_file.read()
binary_content = binary_file.read()

print(f'Text File Content: {text_content}')
print(f'Binary File Content: {binary_content}')

In this example, we define a base class File with read and write methods. Subclasses, TextFile and BinaryFile, override these methods to read and write text and binary data, respectively. This allows us to work with different file types in a uniform way, demonstrating the power of polymorphism.

Advantages of Inheritance and Polymorphism

Inheritance and polymorphism offer several advantages in object-oriented programming:

  1. Code Reusability: Inheritance enables you to reuse code by inheriting attributes and behaviors from a base class, reducing redundancy and promoting a cleaner code structure.
  2. Specialization: Subclasses can specialize by adding their unique attributes and methods while inheriting common features from a parent class.
  3. Polymorphism: Polymorphism allows you to write more flexible and generic code that can work with a variety of objects without knowing their specific types, promoting extensibility.
  4. Hierarchy and Organization: Inheritance allows you to create a hierarchy of classes that can represent complex real-world relationships in a structured and organized manner.
  5. Easier Maintenance: Inheritance simplifies the maintenance of code since changes made in a base class automatically affect all subclasses.

Common Pitfalls

While inheritance and polymorphism are powerful concepts, they should be used judiciously to avoid common pitfalls:

  1. Overuse of Inheritance: Overcomplicated inheritance hierarchies can lead to code that is hard to understand and maintain. Keep hierarchies simple and avoid excessive levels of inheritance.
  2. Method Overriding: Carefully consider which methods to override in subclasses. Overriding too many methods can make the code complex and error-prone.
  3. Design Considerations: Plan your class hierarchy carefully. Make sure that inheritance accurately reflects the “is-a” relationship. In other words, a subclass should be a specific type of the parent class.
  4. Dynamic Behavior: Be cautious when using polymorphism in dynamic languages like Python. Since Python is dynamically typed, method resolution happens at runtime, which can lead to unexpected behavior if not managed properly.
Understanding Inheritance and Polymorphism in Python
Photo by Juanjo Jaramillo on Unsplash

Inheritance and polymorphism are fundamental concepts in object-oriented programming that empower developers to create efficient, maintainable, and flexible code. In Python, these concepts are fully supported and are widely used to model real-world scenarios, promote code reuse, and simplify complex systems.

By understanding and applying inheritance and polymorphism in your programming endeavors, you can design more organized and efficient code that is better equipped to adapt to changing requirements and handle complex real-world scenarios.

Happy exploring!

--

--

NIBEDITA (NS)

Tech enthusiast, Content Writer and lifelong learner! Sharing insights on the latest trends, innovation, and technology's impact on our world.