Python - Polymorphism

9.
What is the significance of the @abstractmethod decorator in polymorphism?

The @abstractmethod decorator in Python is used to declare abstract methods within abstract classes. An abstract method is a method that is declared in an abstract class but has no implementation in the abstract class itself. It must be implemented by any concrete (non-abstract) subclass. The @abstractmethod decorator enforces the requirement for subclasses to provide a concrete implementation of the abstract method.

Example Program:

from abc import ABC, abstractmethod

# Abstract Class with Abstract Method
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

# Concrete Subclass implementing Abstract Method
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

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

# Concrete Subclass implementing Abstract Method
class Square(Shape):
    def __init__(self, side):
        self.side = side

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

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

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

# Using polymorphism with abstract classes
area1 = print_area(circle)
area2 = print_area(square)

print(area1)
print(area2)

Output:

78.5
16

In this example, the @abstractmethod decorator is applied to the area method within the abstract class Shape. This ensures that any concrete subclass of Shape must provide a concrete implementation of the area method. The Circle and Square classes, as concrete subclasses, implement the area method, fulfilling the requirement imposed by the @abstractmethod decorator.

The @abstractmethod decorator helps in enforcing a consistent interface among different classes, promoting code structure and design that supports polymorphic behavior.


10.
How do you achieve polymorphism using class inheritance in Python?

Polymorphism using class inheritance in Python is achieved by creating a hierarchy of classes where a subclass inherits from a superclass. The subclass can override methods of the superclass, providing its own implementation. This allows instances of the subclass to be treated as instances of the superclass, demonstrating polymorphic behavior.

Example Program:

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

# Subclass inheriting from Animal
class Dog(Animal):
    def speak(self):
        return "Dog barks"

# Subclass inheriting from Animal
class Cat(Animal):
    def speak(self):
        return "Cat meows"

# Function demonstrating polymorphism using class inheritance
def animal_speak(animal):
    return animal.speak()

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

# Using polymorphism with class inheritance
result1 = animal_speak(dog)
result2 = animal_speak(cat)

print(result1)
print(result2)

Output:

Dog barks
Cat meows

In this example, the Animal class is the superclass with a method speak. The Dog and Cat classes are subclasses of Animal and override the speak method with their own implementations.

The animal_speak function demonstrates polymorphism by accepting any object of type Animal or its subclasses. This allows the function to work with different types of animals without knowing their specific implementations, showcasing the polymorphic behavior achieved through class inheritance in Python.


11.
Discuss the concept of duck typing in Python and its relation to polymorphism.

Duck typing is a concept in Python that focuses on the behavior of an object rather than its type or class. The idea is that if an object behaves like a certain type, it can be considered an instance of that type, even if it does not explicitly inherit from that type. Duck typing allows for more flexible and dynamic code, promoting polymorphism based on the object's capabilities rather than its formal type.

Example Program:

# Duck-typed classes
class Dog:
    def speak(self):
        return "Dog barks"

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

# Function demonstrating duck typing and polymorphism
def animal_speak(animal):
    return animal.speak()

# Creating objects of duck-typed classes
dog = Dog()
cat = Cat()

# Using polymorphism with duck typing
result1 = animal_speak(dog)
result2 = animal_speak(cat)

print(result1)
print(result2)

Output:

Dog barks
Cat meows

In this example, the Dog and Cat classes do not share a common base class, but they both have a speak method. The animal_speak function demonstrates duck typing by accepting any object with a speak method, regardless of its formal type or class inheritance.

Duck typing promotes polymorphism based on the object's behavior rather than its type. If an object quacks like a duck (behaves like a certain type), it can be treated as a duck (instance of that type), emphasizing the importance of the object's capabilities in dynamic and flexible programming.


12.
What is operator overloading, and how does it contribute to polymorphism?

Operator overloading in Python allows you to define how an operator behaves for objects of a user-defined class. By overloading operators, you can customize their functionality for instances of your class. This contributes to polymorphism by enabling the same operator to perform different actions based on the types of the operands, allowing for more intuitive and flexible code.

Example Program:

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

    # Operator overloading for addition
    def __add__(self, other):
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        elif isinstance(other, (int, float)):
            return Point(self.x + other, self.y + other)
        else:
            raise TypeError("Unsupported operand type")

    # String representation of the object
    def __str__(self):
        return f"Point({self.x}, {self.y})"

# Creating objects of the Point class
point1 = Point(1, 2)
point2 = Point(3, 4)

# Using operator overloading for addition
result1 = point1 + point2
result2 = point1 + 5

print(result1)
print(result2)

Output:

Point(4, 6)
Point(6, 7)

In this example, the Point class defines the __add__ method to overload the addition operator (+). The method checks the type of the operand and performs different actions based on whether it is another Point object or a scalar value (int or float).

Using operator overloading, the addition operator behaves polymorphically for different types of operands. This enables more concise and intuitive code, contributing to the overall flexibility and readability of the program.