Posted on 6/18/2025 5:56:51 PM by Admin

Object Oriented Programming in Python: Inheritance and Polymorphism

In the previous article, I introduced you to the object-oriented programming paradigm and explained the fundamental concepts of classes and objects. You saw how they encapsulate the attributes and methods within them and how each object carries its own set of these attributes. Now, we are all set to get started with two more compelling concepts of OOP, i.e., inheritance and polymorphism. Inheritance and polymorphism enable us to achieve remarkable code reusability, extend functionality without modifying the existing code, and provide us with ways to handle different object types. By the end of this lesson, you will be able to build class hierarchies and make objects behave differently with similar method calls.


Inheritance

The term inheritance in programming means deriving attributes and methods from one class and creating a new class with its own set of attributes and behaviors. Just like in real life, we inherit our hair, eyes, nose, and other properties from our parents, a derived class inherits attributes from its parent class.

  • The new class is generally referred to as a subclass, derived class, or child class.
  • The class from which another class is derived is called the superclass, base class, or parent class.
  • Inheritance establishes an "is-a" relationship among classes.

Benefits of Inheritance

  • If similar functionality is needed in two classes, it is better to inherit one class from another rather than writing similar code twice (reusability).
  • To have variants, you may want to add more functionality or new features to a class. Inheritance is the best way to achieve it (extensibility).
  • Changes made in the parent class are automatically applied to the child classes (increased maintainability).

Defining a Subclass

Syntax:


class child_class_name(parent_class):
    attributes
    methods

Example:


class Animal: # Parent class
    def __init__(self, name):
        self.name = name
    def speak(self):
        return self.name + " makes a sound."

class Dog(Animal): # Dog is a subclass of Animal class
    def __init__(self, name, breed):
        # Call the parent's (Animal's) __init__ method
        super().__init__(name)
        self.breed = breed # Add a new attribute specific to Dog
    def bark(self): # Add a new method specific to Dog
        return self.name + " barks loudly!"

# Create instances
animal = Animal("Animal")

dog = Dog("Buddy", "Golden Retriever")

print(animal.speak()) # Output: Generic Animal makes a sound.
print(dog.speak())         # Output: Buddy makes a sound. (Inherited from Animal)
print(dog.bark())          # Output: Buddy barks loudly! (Dog's own method)
print(dog.name)            # Output: Buddy (Inherited attribute)

Output:


Animal makes a sound.

Buddy makes a sound.

Buddy barks loudly!

Buddy


Polymorphism

The term polymorphism is derived from a Greek word that means 'many forms.' In programming, when you want a method to behave differently based on the parameters passed or the object they are being called with, we use polymorphism instead of creating multiple functions.

We achieve polymorphism in two ways:

  1. Function overloading.
  2. Function overriding.

Function Overloading

When there are multiple definitions of the same function with different numbers of parameters or return values, we call it function overloading. It is useful in situations where you want a function to perform different actions based on the values passed by the user.

For example, previously, we wrote an add function that took two parameters. What if the user wants to add three or four numbers? This is the type of situation where we use function overloading. Instead of defining three different functions for each type of addition, we use the same name for all functions and define different numbers of parameters in them.

Unfortunately, Python does not support method overloading directly. However, you can achieve that with alternative mechanisms like using variable-length arguments.


def add(*numbers):  # Accepts variable length arguments
    total = 0
    for number in numbers:
        total += number
    return total

print(add(2,3,4))  # returns 9
print(add(1,2))  #returns 3
print(add(10, 20, 30, 40))  # returns 100

Function Overriding

When an inherited class has its own definition of a method defined inside the parent class, it is referred to as method overriding. Whenever an overridden method is called with the child's object, it always behaves as defined within the child class.

To understand this, let's override the speak() method in the Dog class:


class Animal: # Parent class
    def __init__(self, name):
        self.name = name
    def speak(self):
        return self.name + " makes a sound."

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def speak(self):  # Overriding the speak method from Animal
        return self.name + " says Woof!"

    def bark(self):
        return self.name + " barks loudly!"

animal = Animal("Animal")
print(animal.speak())  # Calls the parent class method
dog = Dog("Max", "German Shepherd")
print(dog.speak())  # Calls the child class method

Output:


Animal makes a sound.

Max says Woof!

We have now unveiled two more fundamental concepts of OOP and are ready to dive deep into the concepts of encapsulation and abstraction. We'll discuss those in the next article. Till then, keep practicing.


Sharpen Your Skills with These Next Guides