Bytes

Polymorphism and Inheritance in Python

Last Updated: 10th November, 2024

Inheritance permits new classes to inherit features from existing classes, decreasing redundancy and reusing code. Polymorphism lets objects of diverse types share the same interface, empowering methods to be executed unexpectedly based on the object type. This decreases the code required and streamlines upkeep.

What is Inheritance in Python?

Inheritance could be a feature of object-oriented programming that permits one class to acquire characteristics from another class. In other words, inheritance permits a class to be characterized in terms of another class, which makes it simpler to make and keep up an application.

What is Polymorphism in Python?

Polymorphism allows objects of different types to be treated similarly. In other words, polymorphism allows objects to be treated as a single type of object, even if they are of different types. This means that a single set of code can handle any object, even if the objects are of different types.

Advantages of Inheritance and Polymorphism

Inheritance:

  1. It permits code to be reused and avoids code duplication.
  2. It permits for the creation of hierarchical classifications.
  3. It permits for extensibility, as new subclasses can be made.
  4. It permits for less demanding support and debugging.

Polymorphism:

  1. It permits for the execution of dynamic dispatch and the implementation of interfaces.
  2. It reduces the number of lines of code and makes it simpler to maintain.
  3. It permits for the execution of more generic algorithms.
  4. It permits the execution of more adaptable programs.

Disadvantages of Inheritance and Polymorphism

Inheritance:

  1. Tight coupling: Inheritance makes a firmly coupled relationship among classes, making it troublesome to adjust existing code without breaking other program parts.
  2. Increased complexity: Inheritance can increment the complexity of a program, as increasing classes are included to back the inheritance hierarchy.
  3. Overuse: Inheritance ought to be utilized sparingly because it can lead to overcomplicated designs and implementations that are troublesome to keep up with.

Polymorphism:

  1. Trouble investigating: Polymorphism can make it challenging to investigate code because it can be hard to track the stream of execution when the same code is executing in several ways.
  2. Execution issues: Polymorphism can lead to execution issues, as the framework must check each object's sort to decide which strategy to execute.
  3. Superfluous complexity: Polymorphism can too lead to pointless complexity in the event that it is utilized when a less complex approach would suffice.

Types of Inheritance in Python

Single Inheritance

Single inheritance involves one subclass inheriting from one superclass. This is the simplest form of inheritance and is used when a class is derived from a single base class.

class Animal:
    def speak(self):
        return "Animal sound"

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

dog = Dog()
print(dog.speak())  # Output: "Animal sound"
print(dog.bark())   # Output: "Woof!"

Here, Dog inherits from Animal, gaining access to the speak() method.

Multiple Inheritance

Multiple inheritance occurs when a subclass inherits from more than one superclass. This can be useful but also complex, as it may lead to ambiguity if the same method is defined in multiple parent classes.

class Canine:
    def bark(self):
        return "Bark!"

class Domestic:
    def home(self):
        return "Lives with humans"

class Dog(Canine, Domestic):
    pass

dog = Dog()
print(dog.bark())  # Output: "Bark!"
print(dog.home())  # Output: "Lives with humans"

Here, Dog inherits from both Canine and Domestic, gaining methods from both classes.

Multilevel Inheritance

In multilevel inheritance, a class is derived from another class, which is also derived from another class, creating a chain. This form of inheritance can help build more specific functionalities by extending classes step-by-step.

class Animal:
    def breathe(self):
        return "Breathing"

class Mammal(Animal):
    def feed_milk(self):
        return "Feeding milk"

class Dog(Mammal):
    def bark(self):
        return "Woof!"

dog = Dog()
print(dog.breathe())   # Output: "Breathing"
print(dog.feed_milk()) # Output: "Feeding milk"
print(dog.bark())      # Output: "Woof!"

Here, Dog inherits from Mammal, which inherits from Animal. Each level adds new functionality.

Hierarchical Inheritance

In hierarchical inheritance, multiple subclasses inherit from a single base class. This type of inheritance is useful when you want different subclasses to share common behavior from a single superclass.

class Animal:
    def speak(self):
        return "Animal sound"

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

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

dog = Dog()
cat = Cat()
print(dog.speak())  # Output: "Animal sound"
print(dog.bark())   # Output: "Woof!"
print(cat.speak())  # Output: "Animal sound"
print(cat.meow())   # Output: "Meow!"

Here, both Dog and Cat inherit from Animal, sharing the speak() method from the superclass.

Hybrid Inheritance

Hybrid inheritance is a combination of two or more types of inheritance, often used when designing more complex class structures. Python handles the complexity of hybrid inheritance with a specific method resolution order (MRO) that ensures method calls are resolved in a predictable order.

class Animal:
    def speak(self):
        return "Animal sound"

class Mammal(Animal):
    def feed_milk(self):
        return "Feeding milk"

class Bird(Animal):
    def lay_eggs(self):
        return "Laying eggs"

class Platypus(Mammal, Bird):
    def unique_trait(self):
        return "Has traits of both mammals and birds"

platypus = Platypus()
print(platypus.speak())       # Output: "Animal sound"
print(platypus.feed_milk())   # Output: "Feeding milk"
print(platypus.lay_eggs())    # Output: "Laying eggs"
print(platypus.unique_trait())# Output: "Has traits of both mammals and birds"

In this example, Platypus inherits from both Mammal and Bird, combining multiple inheritance types in a hybrid structure.

Working with Inheritance and Polymorphism in Python

Inheritance in Python refers to the process by which one class can acquire the attributes and methods of another class. This is done by creating an inheritance relationship between the two classes. The class that is doing the inheriting is referred to as the child class, and the class that is being inherited from is referred to as the parent class. Polymorphism in Python is the ability of one object to take on multiple forms. This is done by creating multiple classes inherited from a single base class. Each class can then be used interchangeably, as they all share the same interface. This allows for a great degree of flexibility when it comes to programming. To demonstrate how to work with inheritance and polymorphism in Python, consider the following inheritance in python example of a class hierarchy for a game character.

class Character:
    def **init**(self, health, attack):
        self.health = health
        self.attack = attack
class Wizard(Character):
    def **init**(self, health, attack, magic):
        super().**init**(health, attack)
        self.magic = magic
class Warrior(Character):
    def **init**(self, health, attack, magic):
        super().**init**(health, attack,magic)

This code creates a class hierarchy for a game character. It starts with the base class Character, which has two attributes: health and attack. It then creates two child classes, Wizard and Warrior, which both inherit from the Character. The Wizard class adds a new attribute, magic, while the Warrior class does not add any additional attributes. This allows different types of characters to be created using the same interface.

Inheritance in Python with Examples

class Animal:
    """A generic animal"""
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    """A dog, a sub-class of Animal"""
    def bark(self):
        print("Woof!")

fido = Dog("Fido")
fido.bark() # prints "Woof!"

This code creates a class Animal and a class Dog, which is a sub-class of Animal. The Dog class inherits the init  method from Animal and adds its bark() method. The code then creates an instance of Dog, named Fido, and calls the bark() method on it.

Polymorphism in Python Examples

def print_name(obj):
    print(obj.name)

fido = Dog("Fido")
bob = Cat("Bob")

print_name(fido) # prints "Fido

This code demonstrates polymorphism in Python. The function print_name() takes an object as a parameter and prints out the object's name. The print_name() function can be used with any object with the name attribute, regardless of its class. In this example, it is used with a Dog and a Cat object, both subclasses of Animals.

Function Polymorphism in Python

Function polymorphism allows functions to operate on different types of inputs with a consistent interface. In Python, this is possible because it is a dynamically typed language and uses "duck typing," where the functionality depends on the object’s behavior rather than its specific type.

Example of Function Polymorphism with the len() Function

The built-in len() function in Python is a common example of function polymorphism. It can accept various types of inputs (such as lists, strings, and dictionaries) and return the length of the object accordingly.

print(len("Hello"))        # Output: 5 (for a string)
print(len([1234]))   # Output: 4 (for a list)
print(len({"a"1"b"2}))  # Output: 2 (for a dictionary)

In this example, len() behaves differently based on the input type, demonstrating function polymorphism in Python.

Custom Example with a Polymorphic Function

You can also create your own polymorphic function that handles various data types.

def add(a, b):
    return a + b

print(add(5, 10))          # Output: 15 (integers)
print(add("Hello, ""World!"))  # Output: "Hello, World!" (strings)
print(add([1, 2], [3, 4])) # Output: [1, 2, 3, 4] (lists)

Here, the add() function behaves differently based on the type of the arguments, enabling function polymorphism.

Class Polymorphism in Python

Class polymorphism allows objects of different classes to be used interchangeably when they share the same interface. In Python, this is typically achieved using inheritance and method overriding. With class polymorphism, a single function can operate on objects of different classes without knowing their specific types.

Example of Class Polymorphism with a Common Method

In the following example, the classes Cat and Dog inherit from a common superclass Animal, and each subclass defines its own speak() method. A function can then use polymorphism to call speak() on an Animal object, regardless of whether it is a Cat or Dog.

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())

dog = Dog()
cat = Cat()

animal_sound(dog)  # Output: "Woof!"
animal_sound(cat)  # Output: "Meow!"

In this example, animal_sound() can take any Animal object and call the speak() method, demonstrating class polymorphism. The exact method that gets called depends on the specific class of the object passed to animal_sound().

Types of Polymorphism in Python

Compile-Time Polymorphism (Static Binding):

In Python, compile-time polymorphism is primarily achieved through function overloading, although Python does not support true function overloading. However, you can define functions with the same name in Python, but only the latest defined function will be considered.

def add(a, b):
    return a + b

def add(a, b, c):
    return a + b + c

result = add(2, 3)  # Error: Only the latest defined function is available

Run-Time Polymorphism (Dynamic Binding):

Run-time polymorphism in Python is typically achieved through method overriding. You can override methods in a subclass to provide specific implementations. The method that gets called is determined at runtime based on the object's actual type.

class Animal:
    def make_sound(self):
        print("Animal makes a sound")

class Dog(Animal):
    def make_sound(self):
        print("Dog barks")

my_animal = Dog()
my_animal.make_sound()  # Calls the overridden method in the Dog class

Interface Polymorphism (Polymorphism through Duck Typing):

Python follows a concept called "duck typing," which is a form of interface polymorphism. If an object behaves like a particular interface (has the required methods and attributes), it can be treated as an instance of that interface.

class Bird:
    def fly(self):
        pass

class Sparrow(Bird):
    def fly(self):
        print("Sparrow flies")

class Airplane:
    def fly(self):
        print("Airplane flies")

def perform_flight(flying_object):
    flying_object.fly()

sparrow = Sparrow()
airplane = Airplane()

perform_flight(sparrow)  # Output: Sparrow flies
perform_flight(airplane)  # Output: Airplane flies

Operator Overloading:

Python supports operator overloading by defining special methods with double underscores (e.g., __add__, __sub__, __eq__, etc.). These methods allow you to define how operators should behave for objects of your class.

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

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

v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2  # Calls the __add__ method, resulting in v3 = Vector(4, 6)

Parametric Polymorphism (Generics):

Python supports parametric polymorphism through the use of generics using type hints and annotations. You can use type variables to indicate that a function or class can work with various data types.

from typing import TypeVar

T = TypeVar('T')

def print_item(item: T):
    print(item)

print_item(42)         # Works with integers
print_item("Hello")    # Works with strings

Python provides flexibility in achieving different types of polymorphism, making it a versatile language for object-oriented programming.

Composition over Inheritance

Composition and inheritance are both fundamental techniques for reusing code in object-oriented programming, but they serve different purposes. Composition over inheritance is a principle that encourages creating complex types by combining simpler, reusable objects rather than through extending classes.

Why Use Composition Over Inheritance?

  1. Flexibility: Composition allows you to change behaviors at runtime by composing objects in different ways, whereas inheritance is fixed at compile time.
  2. Reduced Coupling: Composition leads to looser coupling between classes because the composed objects are separate and can be replaced or modified without affecting the primary class.
  3. Better for "Has-a" Relationships: Composition is ideal for "has-a" relationships, while inheritance is more suitable for "is-a" relationships. For example, a Car has an Engine, but it is not an engine.
  4. Avoids Complex Inheritance Hierarchies: Inheritance can lead to deep and complex hierarchies that are hard to manage and extend, whereas composition enables simpler, more maintainable structures.

Example of Composition Over Inheritance

In the following example, a Car class is composed of an Engine and a Wheel, rather than inheriting from either. This allows the Car class to use and manage these objects without forming a rigid inheritance structure.

class Engine:
    def start(self):
        return "Engine started."

class Wheel:
    def rotate(self):
        return "Wheel is rotating."

class Car:
    def __init__(self):
        self.engine = Engine()
        self.wheels = [Wheel() forin range(4)]
    
    def start(self):
        return self.engine.start()
    
    def move(self):
        wheel_actions = [wheel.rotate() for wheel in self.wheels]
        return f"Car is moving with: {', '.join(wheel_actions)}"

my_car = Car()
print(my_car.start())     # Output: "Engine started."
print(my_car.move())      # Output: "Car is moving with: Wheel is rotating, Wheel is rotating, ..."

In this example, Car has an Engine and multiple Wheel objects. If we need to swap out the Engine or change the type of Wheel, we can do so without modifying the Car class, showcasing the flexibility of composition.

Limitations of Inheritance and Polymorphism

Inheritance and polymorphism are useful tools for software development and object-oriented programming. However, there are several limitations to be aware of when using inheritance and polymorphism.

  1. Unnecessary complexity: Inheritance and polymorphism can add unnecessary complexity to software design if not used carefully. This can result in software that is difficult to maintain and debug.
  2. Decreased performance: Inheritance and polymorphism can reduce software performance due to the additional overhead associated with their use.
  3. Fragile base class problem: The "Fragile Base Class Problem" occurs when changes to a base class cause unexpected behavior in child classes.
  4. Limited reusability: Inheritance and polymorphism can limit code reusability, as it is often difficult to reuse code based on inheritance or polymorphism.
  5. Lack of flexibility: Inheritance and polymorphism can lack the flexibility necessary for some software designs. For example, if a software design requires the ability to extend the behavior of an object dynamically, then there may be better approaches than inheritance and polymorphism.

Real-world Applications of Inheritance and Polymorphism

To see inheritance and polymorphism in action, let's look at some practical, real-world scenarios where these concepts shine:

1. GUI Frameworks

Graphical User Interface (GUI) frameworks often use inheritance and polymorphism. For instance, consider a base Widget class with child classes like Button, TextBox, and Label. Each subclass inherits from Widget and can override methods such as render() to display the widget differently on the screen.

class Widget:
    def render(self):
        pass

class Button(Widget):
    def render(self):
        print("Rendering a button")

class TextBox(Widget):
    def render(self):
        print("Rendering a text box")

widgets = [Button(), TextBox()]
for widget in widgets:
    widget.render()  # Outputs "Rendering a button" and "Rendering a text box"

This setup allows a GUI framework to treat all widgets uniformly, even though each widget renders differently.

2. Payment Processing System

In a payment processing system, you might have different types of payment methods such as CreditCard, PayPal, and BankTransfer. A base PaymentMethod class can define the structure, and subclasses can implement specific processing logic.

3. Game Development

In games, inheritance and polymorphism are commonly used to create entities with different behaviors. For example, a Character base class can have subclasses like Player, Enemy, and NPC, each implementing unique behaviors.

4. File I/O System

In applications dealing with multiple file formats, you can use inheritance and polymorphism to handle each format using a base FileHandler class. Subclasses like TextFileHandler, CSVFileHandler, and JSONFileHandler can each implement their own read() and write() methods.

Conclusion

Inheritance in object-oriented programming permits one class to inherit characteristics from another, resulting in less demanding code reuse, the creation of hierarchical classifications, and extensibility. Polymorphism empowers distinctive object types to share the same interface, driving the execution of more non-specific algorithms, more adaptable programs, and fewer lines of code. In any case, inheritance can increment program complexity and lead to tight coupling, whereas polymorphism can lead to investigating challenges and execution issues.

Key Takeaways

  1. Inheritance permits you to define a class that acquires from another class, giving it access to all the attributes and methods of the parent class. This permits code reuse and helps you make more productive code.
  2. Polymorphism permits you to characterize methods within the parent class that can be overridden within the child class. This allows for a more secluded and flexible codebase because it permits distinctive objects to reply differently to the same method calls.
  3. Both inheritance and polymorphism are effective programming techniques. When utilized legitimately, they can assist you in making more organized, effective, and robust code.
  4. Python gives a number of features that make it simpler to utilize inheritance and polymorphism in your code. These incorporate extraordinary strategies like init() and str(), as well as the utilization of super() to get to the parent class.
  5. Inheritance and polymorphism can be utilized together to form effective and extensible code. Be that as it may, it's imperative to use them appropriately, as they can lead to difficult-to-debug blunders in case not actualized accurately.

Quiz

  1. In object-oriented programming, what is the term for the ability of a class to use the methods and properties of another class? 
    1. Polymorphism 
    2. Inheritance 
    3. Abstraction 
    4. Encapsulation

Answer:b. Inheritance

  1. What is the purpose of polymorphism in object-oriented programming? 
    1. To reduce code duplication 
    2. To abstract away implementation details  
    3. To create objects of different types  
    4. To allow a class to use the methods and properties of another class

Answer:d. To allow a class to use the methods and properties of another class

  1. When a class inherits from another class, what is the term for the class from which the other class inherits?
    1. Subclass 
    2. Base class 
    3. Parent class 
    4. Superclass

Answer:d. Superclass

  1. What is the purpose of overriding a method in object-oriented programming? 
    1. To add additional functionality to a method 
    2. To change the parameters of a method  
    3. To make a method accessible to classes outside of its scope 
    4. To replace the functionality of an existing method

Answer:d. To replace the functionality of an existing method

Module 7: OOPs in PythonPolymorphism and Inheritance in Python

Top Tutorials

Related Articles

  • Official Address
  • 4th floor, 133/2, Janardhan Towers, Residency Road, Bengaluru, Karnataka, 560025
  • Communication Address
  • Follow Us
  • facebookinstagramlinkedintwitteryoutubetelegram

© 2024 AlmaBetter