Exercises: Inheritance and Polymorphism
These exercises progress from basic subclassing through polymorphic design to real-world architecture decisions. All code should run on Python 3.12+.
Difficulty Guide: - ⭐ Foundational (5-10 min each) - ⭐⭐ Intermediate (10-20 min each) - ⭐⭐⭐ Challenging (20-40 min each) - ⭐⭐⭐⭐ Advanced/Research (40+ min each)
Part A: Inheritance Basics ⭐
A.1. Create a Vehicle class with attributes make, model, and year, and a describe() method that returns a string like "2024 Toyota Camry". Then create a Truck subclass that adds a payload_capacity attribute (in tons) and overrides describe() to include it. Create instances of both and print their descriptions.
A.2. Given this parent class:
class Shape:
def __init__(self, color: str):
self.color = color
def describe(self) -> str:
return f"A {self.color} shape"
Create a Circle subclass that takes color and radius, calls super().__init__(), and overrides describe() to return something like "A red circle with radius 5.0". Verify with:
c = Circle("red", 5.0)
print(c.describe()) # A red circle with radius 5.0
print(isinstance(c, Shape)) # True
A.3. What is the output of the following code? Predict first, then verify by running it.
class Base:
def greet(self):
return "Hello from Base"
class Child(Base):
pass
class GrandChild(Child):
def greet(self):
return "Hello from GrandChild"
b = Base()
c = Child()
g = GrandChild()
print(b.greet())
print(c.greet())
print(g.greet())
A.4. Explain the difference between these two approaches. Which is correct for creating a subclass that extends the parent's __init__?
# Approach 1
class Student(Person):
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
# Approach 2
class Student(Person):
def __init__(self, name, age, major):
super().__init__(name, age)
self.major = major
A.5. True or false (explain each):
- a) A subclass automatically gets all methods from its parent class.
- b) A subclass can override some methods and inherit others.
- c) isinstance(dog, Animal) returns False if Dog inherits from Animal.
- d) You can create an instance of an abstract base class.
- e) super() can only be used inside __init__.
Part B: Method Overriding and super() ⭐⭐
B.1. Create an Employee class with name, title, and base_salary attributes and a calculate_pay() method that returns base_salary. Then create:
- SalariedEmployee: calculate_pay() returns base_salary / 12 (monthly pay).
- HourlyEmployee: adds hours_worked and hourly_rate; calculate_pay() returns hours_worked * hourly_rate.
- CommissionEmployee: adds sales and commission_rate; calculate_pay() returns base_salary / 12 + sales * commission_rate.
Create one instance of each and print their pay.
B.2. Create a Logger base class with a log(message) method that prints the message with a timestamp. Then create:
- FileLogger: overrides log() to call super().log() AND append the message to a list called self.log_entries.
- PrefixLogger: takes a prefix in __init__ and overrides log() to prepend the prefix before calling super().log().
Demonstrate that FileLogger both prints and stores, and that PrefixLogger adds a prefix.
B.3. Debug this code. It has two bugs — find and fix both:
class Animal:
def __init__(self, name: str, legs: int):
self.name = name
self.legs = legs
def speak(self) -> str:
return f"{self.name} makes a sound"
class Cat(Animal):
def __init__(self, name: str, indoor: bool):
self.indoor = indoor
def speak(self) -> str:
return f"{self.name} says Meow"
def describe(self) -> str:
location = "indoor" if self.indoor else "outdoor"
return f"{self.name} is an {location} cat with {self.legs} legs"
whiskers = Cat("Whiskers", indoor=True)
print(whiskers.describe())
B.4. Create a Notification base class with a send(message) method. Create three subclasses: EmailNotification, SMSNotification, and PushNotification. Each should override send() to return a string describing how the notification was sent (e.g., "Email sent: Meeting at 3pm"). Then write a function broadcast(notifications, message) that sends the message through all notification channels polymorphically.
B.5. Without running the code, determine the output. Then verify:
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B+" + super().method()
class C(B):
def method(self):
return "C+" + super().method()
obj = C()
print(obj.method())
Part C: Polymorphism ⭐⭐
C.1. Create an abstract base class Converter with an abstract method convert(value: float) -> float and a concrete method convert_all(values: list[float]) -> list[float] that applies convert to every element. Then create:
- CelsiusToFahrenheit: converts C to F.
- KilometersToMiles: converts km to miles (1 km = 0.621371 mi).
- KilogramsToPounds: converts kg to lbs (1 kg = 2.20462 lbs).
Demonstrate polymorphism by putting all three converters in a list and calling convert_all() on each.
C.2. Elena needs report validators. Create an abstract Validator class with an abstract method validate(data: list[dict]) -> list[str] that returns a list of error messages (empty if valid). Create:
- RequiredFieldValidator(fields): checks that every row has all required fields.
- RangeValidator(field, min_val, max_val): checks that a numeric field is within range.
- UniqueValidator(field): checks that all values for a field are unique.
Write a function run_validators(validators, data) that runs all validators polymorphically and collects all errors.
C.3. Modify the text adventure Item hierarchy to add a Scroll class (which has a spell attribute and a use() that casts the spell) and a Armor class (which has a defense attribute and a use() that equips the armor). Demonstrate that existing code using item.use(player) works with the new types without modification.
C.4. Python's built-in len(), str(), and + operator are all polymorphic. For each one, demonstrate three different types that respond to it, and explain how each type implements the behavior differently. (Hint: think about __len__, __str__, __add__.)
C.5. Explain why this function is a poor design and rewrite it using polymorphism:
def calculate_area(shape):
if isinstance(shape, Circle):
return 3.14159 * shape.radius ** 2
elif isinstance(shape, Rectangle):
return shape.width * shape.height
elif isinstance(shape, Triangle):
return 0.5 * shape.base * shape.height
else:
raise ValueError("Unknown shape")
Part D: Abstract Base Classes ⭐⭐-⭐⭐⭐
D.1. Create an abstract Database class with abstract methods connect(), query(sql), and close(). Create two concrete subclasses: SQLiteDatabase and MockDatabase (the mock version stores queries in a list instead of executing them). Demonstrate that you cannot instantiate Database directly, and that both subclasses work interchangeably.
D.2. Create an abstract PaymentProcessor with abstract methods charge(amount) and refund(amount). Create CreditCardProcessor and PayPalProcessor subclasses. Write a checkout(processor, amount) function that works with either processor. What happens if you create a subclass that forgets to implement refund()?
D.3. You have an abstract Sorter class with an abstract method sort(data: list) -> list. Create three concrete subclasses that implement different sorting strategies: BubbleSorter, InsertionSorter, and PythonSorter (which just calls Python's built-in sorted()). Write a benchmark function that times all three on the same data and prints which is fastest.
Part E: Composition vs. Inheritance ⭐⭐⭐
E.1. A Smartphone has a Camera, a Battery, and a Screen. Design these four classes using composition. The Smartphone should be able to: take a photo (delegates to Camera), check battery level (delegates to Battery), and display text (delegates to Screen). Do NOT use inheritance.
E.2. Refactor this inheritance-based design to use composition instead. Explain why composition is better here:
class HTMLElement:
def __init__(self, tag: str, content: str):
self.tag = tag
self.content = content
def render(self) -> str:
return f"<{self.tag}>{self.content}</{self.tag}>"
class StyledHTMLElement(HTMLElement):
def __init__(self, tag: str, content: str, css_class: str):
super().__init__(tag, content)
self.css_class = css_class
def render(self) -> str:
return f'<{self.tag} class="{self.css_class}">{self.content}</{self.tag}>'
E.3. Design a Character class for a role-playing game. A character has a name, health, and carries an inventory of items. The character also has an equipped weapon and equipped armor. Should Character inherit from Item? From Weapon? Justify your answer and implement the class using the appropriate combination of inheritance and composition.
E.4. For each of the following relationships, decide whether inheritance or composition is more appropriate and explain why:
- a) University and Department
- b) SavingsAccount and BankAccount
- c) Playlist and Song
- d) ElectricCar and Car
- e) EmailMessage and Attachment
- f) AdminUser and User
Part F: Design Challenges ⭐⭐⭐-⭐⭐⭐⭐
F.1. Design and implement a small library system with these classes:
- LibraryItem (abstract): title, checked_out status, abstract checkout() and return_item() methods
- Book: author, isbn
- DVD: director, runtime
- Magazine: issue_number, month
Each type should have different checkout durations (books: 21 days, DVDs: 7 days, magazines: 14 days). Write a function that processes a list of items polymorphically, checking out all available items and printing their due dates.
F.2. Build a simple expression evaluator using inheritance and polymorphism. Create an abstract Expression class with an abstract evaluate() method. Create:
- Number(value): returns the value
- Add(left, right): evaluates left + right
- Multiply(left, right): evaluates left * right
- Negate(expr): evaluates -expr
Build the expression (3 + 4) * -(2 + 5) using your classes and evaluate it. (Answer: -49)
F.3. (Research) Read about the "Liskov Substitution Principle" (LSP). Find a real example in a Python library (such as the collections.abc module) where this principle is applied. Write a short explanation (3-4 paragraphs) of how the library uses abstract base classes and why the principle matters for users of that library.
F.4. (Research) Compare Python's approach to polymorphism (duck typing, no required interface declarations) with Java's approach (explicit interface declarations, static type checking). What are the trade-offs? When might Python's flexibility become a liability? Write your analysis with concrete code examples in both languages (or pseudocode for Java if you haven't learned it).