Python - Polymorphism

17.
How do you achieve runtime polymorphism using method overriding?

Runtime polymorphism in Python is achieved through method overriding, where a subclass provides a specific implementation for a method that is already defined in its superclass. This allows instances of the subclass to be treated as instances of the superclass, and the correct method implementation is determined at runtime based on the actual object type.

Example Program:

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

# Subclass overriding the speak method
class Dog(Animal):
    def speak(self):
        return "Dog barks"

# Subclass overriding the speak method
class Cat(Animal):
    def speak(self):
        return "Cat meows"

# Function demonstrating runtime polymorphism
def animal_speak(animal):
    return animal.speak()

# Creating objects of different subclasses
animal1 = Animal()
dog = Dog()
cat = Cat()

# Using runtime polymorphism
result1 = animal_speak(animal1)  # Animal speaks
result2 = animal_speak(dog)      # Dog barks
result3 = animal_speak(cat)      # Cat meows

print(result1)
print(result2)
print(result3)

Output:

Animal speaks
Dog barks
Cat meows

In this example, the Animal class defines a method speak. The Dog and Cat subclasses override this method with their own implementations. The animal_speak function demonstrates runtime polymorphism by accepting objects of different types (including the superclass and its subclasses) and invoking the correct speak method based on the actual object type.

Runtime polymorphism allows for flexibility and extensibility in code, enabling the use of a common interface (such as the speak method in this example) across different classes, making the code more adaptable to changes and additions in the future.


18.
Discuss the use of polymorphism in the context of abstract base classes (ABCs).

In Python, abstract base classes (ABCs) provide a way to define abstract interfaces and enforce certain methods to be implemented by concrete subclasses. Polymorphism plays a crucial role in the context of ABCs, allowing objects of different classes to be treated as instances of a common base class. This ensures a consistent interface across multiple implementations, promoting code reliability and maintainability.

Example Program:

from abc import ABC, abstractmethod

# Abstract base class defining a common interface
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

# Concrete subclasses implementing the abstract interface
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

# Function demonstrating polymorphism with abstract base classes
def print_area(shape):
    return shape.area()

# Creating objects of different subclasses
circle = Circle(5)
square = Square(4)

# Using polymorphism with abstract base classes
result1 = print_area(circle)
result2 = print_area(square)

print(result1)
print(result2)

Output:

78.5
16

In this example, the Shape class serves as an abstract base class with the abstract method area. The Circle and Square classes are concrete subclasses that implement the area method. The print_area function demonstrates polymorphism by accepting objects of different subclasses of Shape and invoking their area method.

Using polymorphism with abstract base classes ensures a consistent interface for various subclasses, allowing for flexibility and extensibility in code. It promotes a design that supports future additions of new shapes without modifying the existing codebase.


19.
What are some advantages of using polymorphism in software design?

Polymorphism is a key concept in software design that offers several advantages, enhancing code flexibility, reusability, and maintainability. Here are some advantages of using polymorphism:

1. Code Reusability: Polymorphism allows the same interface to be used across different classes. This promotes the reuse of code and the implementation of common functionality in a shared interface.

2. Flexibility and Extensibility: Polymorphism enables the addition of new classes or types without modifying existing code. This makes the system more flexible and extensible, allowing for easy adaptation to changing requirements.

3. Consistent Interfaces: By using a common interface or abstract base class, polymorphism ensures a consistent and standardized way to interact with objects of different types. This leads to a more predictable and user-friendly codebase.

4. Maintenance Ease: With polymorphism, modifications or enhancements can be made to individual classes without affecting the entire system. This makes maintenance and updates more straightforward and reduces the risk of introducing errors.

Example Program:

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

class Dog(Animal):
    def speak(self):
        return "Dog barks"

class Cat(Animal):
    def speak(self):
        return "Cat meows"

def animal_speak(animal):
    return animal.speak()

# Creating objects of different subclasses
animal1 = Animal()
dog = Dog()
cat = Cat()

# Using polymorphism to demonstrate advantages
result1 = animal_speak(animal1)
result2 = animal_speak(dog)
result3 = animal_speak(cat)

print(result1)
print(result2)
print(result3)

Output:

Animal speaks
Dog barks
Cat meows

In this example, polymorphism is used to create a consistent interface for different types of animals. The animal_speak function accepts objects of different types and invokes the correct speak method based on their actual type, showcasing the advantages of polymorphism.

Overall, polymorphism is a powerful design principle that contributes to code clarity, maintainability, and adaptability in software development.


20.
How can you implement polymorphism without using inheritance?

Polymorphism can be implemented without using inheritance in Python by relying on the concept of duck typing. Duck typing allows objects to be treated based on their behavior rather than their explicit type or inheritance hierarchy. In this approach, the emphasis is on what an object can do rather than what it is.

Example Program:

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

    def area(self):
        return 3.14 * self.radius**2

class Square:
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side**2

def print_area(shape):
    return shape.area()

# Creating objects without a common base class
circle = Circle(5)
square = Square(4)

# Using polymorphism without inheritance
result1 = print_area(circle)
result2 = print_area(square)

print(result1)
print(result2)

Output:

78.5
16

In this example, the Circle and Square classes do not share a common base class or use inheritance. However, both classes have a method named area, allowing them to be used polymorphically with the print_area function. The function relies on the behavior of the objects rather than their explicit types.

This approach demonstrates the flexibility of duck typing, allowing objects with different structures to be used in a polymorphic way as long as they exhibit the required behavior. It promotes code that is more adaptable to changes and supports a wider range of object types.