Explore programming tutorials, exercises, quizzes, and solutions!
Python Polymorphism Exercises
1/19
In object-oriented programming, polymorphism refers to the ability of different classes to be treated as instances of the same class through a common interface, even though their behaviors may vary. Python supports polymorphism through method overriding and duck typing — where an object’s suitability is determined by the presence of a method, not its actual type.
Polymorphism is useful when you want to write code that can work with objects of different classes interchangeably, as long as they implement the expected behavior.
Consider the following Python code:
class Bird:
def make_sound(self):
return "Chirp"
class Dog:
def make_sound(self):
return "Bark"
def play_sound(animal):
print(animal.make_sound())
play_sound(Bird())
play_sound(Dog())
What concept does this example demonstrate?
This code demonstrates polymorphism in action using duck typing, a Pythonic approach where the type of an object is less important than the methods it implements.
Both Bird and Dog define a method make_sound().
The function play_sound() expects any object that has a make_sound() method — regardless of its class.
When Bird() and Dog() are passed to play_sound(), each responds according to its own implementation.
This allows Python functions to operate on objects of different types, as long as they follow the expected interface — making code more flexible, reusable, and scalable.
What will be printed when the following code is executed?
The print_details function calls the details() method of the passed objects. The Manager and Developer classes override the details() method of the Employee class, printing "Manager details" and "Developer details" respectively.
What will be the output of the following code?
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def area(self):
return 5 * 4
class Circle(Shape):
def area(self):
return 3.14 * 3 * 3
def print_area(shape):
print(shape.area())
rectangle = Rectangle()
circle = Circle()
print_area(rectangle)
print_area(circle)
The print_area function calls the area() method of the passed object. The Rectangle class returns 20 (5*4), and the Circle class returns 28.26 (3.14*3*3), thus printing the respective areas for each shape.
What will be the output of this code?
class Vehicle:
def move(self):
print("Vehicle is moving")
class Car(Vehicle):
def move(self):
print("Car is moving")
class Bike(Vehicle):
def move(self):
print("Bike is moving")
def start_move(vehicle):
vehicle.move()
car = Car()
bike = Bike()
start_move(car)
start_move(bike)
The start_move function calls the move() method of the passed object. Since both Car and Bike override the move() method of Vehicle, the respective overridden methods are invoked, printing "Car is moving" and "Bike is moving".
What will be the output of the following code?
class Animal:
def sound(self):
return "Some generic sound"
class Dog(Animal):
def sound(self):
return "Woof"
class Cat(Animal):
def sound(self):
return "Meow"
class Bird(Animal):
pass
def animal_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
bird = Bird()
animal_sound(dog)
animal_sound(cat)
animal_sound(bird)
The animal_sound function calls the sound() method of the passed object. Both Dog and Cat classes override the sound() method from Animal, while the Bird class does not, so it inherits the generic sound method from Animal. Thus, the output is "Woof", "Meow", and "Some generic sound" for Dog, Cat, and Bird respectively.
What will be the output of the following code?
class Shape:
def draw(self):
print("Drawing shape")
class Circle(Shape):
def draw(self):
print("Drawing circle")
class Square(Shape):
def draw(self):
print("Drawing square")
def render(shape):
shape.draw()
shapes = [Circle(), Square()]
for shape in shapes:
render(shape)
The render function calls the draw() method of the passed object. Since Circle and Square override the draw() method from Shape, the specific methods are called, printing "Drawing circle" and "Drawing square" respectively.
What will be the output of this code?
class A:
def greet(self):
print("Hello from A")
class B(A):
def greet(self):
super().greet()
print("Hello from B")
class C(B):
def greet(self):
super().greet()
print("Hello from C")
c = C()
c.greet()
The greet() method in class C calls the greet() method of class B using super(), and then class B calls class A's greet() method. Hence, the output is printed in the order "Hello from A", "Hello from B", and "Hello from C".
What will be the output of this code?
class Animal:
def sound(self):
return "Animal sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
pass
def get_sound(animal):
return animal.sound()
dog = Dog()
cat = Cat()
print(get_sound(dog))
print(get_sound(cat))
The Dog class overrides the sound() method from Animal, so it prints "Bark". The Cat class does not override the sound() method, so it inherits the method from Animal, which prints "Animal sound".
Which of the following statements is true about method overriding in Python?
In Python, method overriding allows a subclass to provide its own implementation of a method already defined in the superclass. It is a fundamental concept for achieving polymorphism. Method overriding is not mandatory for polymorphism, but it enables it when subclass methods are invoked dynamically.
The Employee class defines the details() method that outputs the name and salary. The Manager class overrides the details() method to include the department as well. When calling the details() method for both the Employee and Manager instances, the specific implementations are used for each. Thus, the output is as expected: "Employee Name: Alice, Salary: 50000" for the Employee and "Manager Name: Bob, Salary: 70000, Department: HR" for the Manager.
What will be the output of the following code?
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def display_area(shape):
print(f"The area is: {shape.area()}")
rect = Rectangle(5, 10)
circle = Circle(7)
display_area(rect)
display_area(circle)
In this code, there are two classes, Rectangle and Circle, both inheriting from the Shape class. The area method is overridden in both child classes. The display_area function prints the area of each shape. For the rectangle, the area is calculated as width * height, resulting in 50. For the circle, the area is calculated using the formula π * radius^2, yielding 153.86 when radius is 7.
The ElectricVehicle class and GasVehicle class both inherit from the Vehicle class and override the show_details() method. The ElectricVehicle class adds battery_capacity details, while the GasVehicle class adds fuel_type. When we print the details of both vehicles, the output will display the details of the Tesla Model S, including its battery capacity, and the Ford Mustang, including its fuel type. Option1 correctly reflects the output for both vehicle types.
What will be the output of the following code?
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
else:
print("Deposit amount must be positive.")
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
else:
print("Insufficient funds or invalid amount.")
def __str__(self):
return f"Account owner: {self.owner}, Balance: {self.balance}"
# Creating an Account instance
account = Account("John Doe", 1000)
account.deposit(500)
account.withdraw(200)
account.deposit(-50)
account.withdraw(2000)
print(account)
In this code, we have an Account class with methods to deposit, withdraw, and display account details. The deposit method correctly adds the deposit amount to the balance if it’s positive. The withdraw method subtracts the withdrawal amount if it doesn’t exceed the balance. The invalid deposit (-50) is ignored, and the attempt to withdraw 2000 results in an error. After making a valid deposit of 500 and withdrawing 200, the balance is 1300. Option1 correctly reflects this final balance.
What will be the output of the following code?
class Car:
def __init__(self, model, year):
self.model = model
self.year = year
def __str__(self):
return f"{self.model} ({self.year})"
class ElectricCar(Car):
def __init__(self, model, year, battery_capacity):
super().__init__(model, year)
self.battery_capacity = battery_capacity
def __str__(self):
return f"{super().__str__()} with {self.battery_capacity} kWh battery"
car1 = Car("Toyota Camry", 2020)
ev1 = ElectricCar("Tesla Model 3", 2021, 75)
print(car1)
print(ev1)
In this code, the Car class defines a basic constructor and string representation, while the ElectricCar class inherits from Car and adds its own string representation to include the battery capacity. The first print statement outputs the string for the regular car, "Toyota Camry (2020)", while the second print statement calls the ElectricCar's string representation, which includes both the model, year, and battery capacity, "Tesla Model 3 (2021) with 75 kWh battery". Therefore, Option2 is the correct answer, as it correctly orders the outputs for car1 and ev1.
In this tricky example, we define a class A with custom equality behavior by overriding the __eq__ method. The equality check compares the values of two A objects. When creating the set from the list of A objects (a1, a2, a3), the set removes duplicates, but because a1 and a2 have the same value (10), they are considered equal, so only one A(10) remains in the set. The output thus contains A(10) and A(20), reflecting the uniqueness of the objects based on their value, not their identity. Option1 is the correct answer.
What will be the output of the following code?
class CustomDict:
def __init__(self):
self.data = {}
def __setitem__(self, key, value):
self.data[key] = value
def __getitem__(self, key):
if key not in self.data:
raise KeyError(f"Key '{key}' not found!")
return self.data[key]
def __delitem__(self, key):
if key in self.data:
del self.data[key]
else:
raise KeyError(f"Key '{key}' not found!")
custom_dict = CustomDict()
custom_dict["apple"] = 10
custom_dict["banana"] = 20
del custom_dict["apple"]
print(custom_dict["apple"])
This code defines a custom dictionary-like class, CustomDict, that overrides __setitem__, __getitem__, and __delitem__ methods. The program adds "apple" and "banana" with respective values 10 and 20. After deleting the "apple" key, trying to access it again raises a KeyError since it's no longer present in the dictionary. The raised KeyError is caught and its message "Key 'apple' not found!" is printed. Thus, Option1 is the correct answer, as it correctly reflects the error message when trying to access a deleted key.
What will be the output of the following code?
class MyClass:
def __init__(self):
self.data = []
def __getitem__(self, index):
if index < 0:
raise IndexError("Negative index is not allowed!")
return self.data[index]
def __setitem__(self, index, value):
if index < 0:
raise IndexError("Negative index is not allowed!")
self.data.insert(index, value)
def __delitem__(self, index):
if index < 0:
raise IndexError("Negative index is not allowed!")
del self.data[index]
obj = MyClass()
obj[0] = 10
obj[1] = 20
del obj[0]
print(obj[1])
This code defines a custom class, MyClass, that implements special behavior for getting, setting, and deleting items from an internal list. It uses the __getitem__, __setitem__, and __delitem__ methods to manage the data list. In this case, the item at index 0 is set to 10, and the item at index 1 is set to 20. Then, the item at index 0 is deleted, leaving the list as [20]. The print statement accesses the value at index 1, which is now 20. Therefore, Option2 is the correct answer.
What will be the output of the following code?
class MyClass:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x + y
def __add__(self, other):
return self.x + other.x
obj1 = MyClass(10)
obj2 = MyClass(20)
result = obj1 + obj2
print(result)
result = obj1(5)
print(result)
This code demonstrates a class with two special methods: __call__ and __add__. The __call__ method makes an instance of MyClass callable, meaning that obj1(5) will return the value of obj1.x (which is 10) plus the argument passed (5), resulting in 15. The __add__ method allows us to add two MyClass instances together. The sum of obj1.x (10) and obj2.x (20) results in 30. Therefore, the first output is 30 (from obj1 + obj2) and the second output is 15 (from obj1(5)). Option1 is the correct answer.
What will be the output of the following code?
class A:
def __init__(self):
self.value = 10
def show(self):
print(f"A: {self.value}")
class B(A):
def __init__(self):
super().__init__()
self.value = 20
def show(self):
print(f"B: {self.value}")
class C(A):
def __init__(self):
super().__init__()
self.value = 30
def show(self):
print(f"C: {self.value}")
class D(B, C):
def __init__(self):
super().__init__()
def show(self):
print(f"D: {self.value}")
d = D()
d.show()
This is a tricky question because it deals with **multiple inheritance** and the **Method Resolution Order (MRO)** in Python. In the class hierarchy, class D inherits from both class B and class C, and both B and C override the `show()` method. The `super()` function is used in the constructors to call the parent classes' constructors, and because of Python's MRO, class B is called first when `super()` is invoked in D's constructor. As a result, `self.value` is set to 20 from class B. The `show()` method of class D is then called, which prints "D: 20", since it calls the `show()` method of class B. Therefore, Option2 is the correct answer.
Practicing Python Polymorphism? Don’t forget to test yourself later in
our
Python Quiz.
About This Exercise: Python – Polymorphism
Welcome to the Python Polymorphism exercises — a carefully designed set of challenges to help you understand and master one of the core principles of object-oriented programming. Polymorphism allows objects of different classes to be treated as instances of the same class through a common interface, enabling flexible and extensible code design. Whether you’re new to OOP or looking to deepen your Python skills, this section will guide you through practical uses of polymorphism.
Polymorphism in Python is often achieved through method overriding and duck typing, allowing you to write code that can work with different object types without needing to know their exact classes. These exercises will help you learn how to define methods in parent classes and override them in child classes to customize behavior. You’ll also explore how Python’s dynamic typing lets you use polymorphism naturally without strict type declarations.
These challenges include practical scenarios where polymorphism simplifies your code — such as processing different shapes in a graphics program or handling various payment methods in an e-commerce application. By practicing these examples, you’ll gain confidence in writing clean, maintainable code that adapts to changing requirements.
Mastering polymorphism is essential for building scalable applications and designing flexible APIs. It complements other OOP concepts like inheritance and encapsulation, forming the foundation for advanced software architecture and design patterns. These exercises will prepare you for real-world coding and technical interviews that test your understanding of object-oriented design.
In addition to hands-on problems, this section highlights best practices like designing clear interfaces, avoiding unnecessary complexity, and leveraging Python’s dynamic features effectively. Combining these skills with knowledge of classes, inheritance, and exception handling will round out your Python programming expertise.
We recommend supplementing your practice with related topics such as abstract classes and interfaces (using Python’s abc module), duck typing, and design patterns. Taking quizzes and reviewing multiple-choice questions on polymorphism can also reinforce your learning.
Start practicing the Python Polymorphism exercises today to enhance your ability to write adaptable, reusable, and maintainable Python code. With consistent effort, you’ll be ready to tackle complex software design challenges confidently.