Inheritance and Polymorphism
Problem Statement
Implement a class hierarchy for shapes.
Background
Inheritance allows classes to share behavior:
class Child(Parent):
def __init__(self):
super().__init__()
Your Task
Create an inheritance hierarchy:
-
Base class Shape with:
- name attribute
- area() method (returns 0)
- str returning "{name}: area={area}"
-
Rectangle(Shape) with:
- Constructor taking width and height
- area() returning width * height
-
Circle(Shape) with:
- Constructor taking radius
- area() returning π * radius²
Example:
Rectangle(4, 5)
Rectangle: area=20
4 * 5 = 20
Constraints:
- Use math.pi for π
- Round area to 2 decimal places in str
Background Knowledge
Inheritance enables a class (child/subclass) to inherit attributes and methods from a base class (parent/superclass), promoting code reuse and establishing an "is-a" relationship. In Python, this is declared using class Child(Parent):, and super() calls the parent's methods, such as in constructors. For example, a Shape base class can define common behavior like reporting area, which derived classes override.
Polymorphism allows objects of different classes to be treated interchangeably through a common interface, often via method overriding. Here, Rectangle and Circle both implement area(), but compute it differently—enabling uniform calls like shape.area() regardless of type. Python's duck typing ("if it walks like a duck...") supports this dynamically, while str ensures consistent string representation across the hierarchy.
This hierarchy models real-world abstraction: all shapes share a name and area concept, but specifics vary, demonstrating OOP principles like encapsulation (data/method bundling) and extensibility.
Algorithm/Approach
Use a single inheritance tree rooted at Shape:
- Define abstract-like base behavior in Shape (default area(), common str).
- Extend with subclasses Rectangle and Circle, overriding area() via specific formulas.
- Leverage super().init() for shared initialization and polymorphism for interchangeable use.
This top-down design ensures subclasses specialize without duplicating code, aligning with Python's Method Resolution Order (MRO) for single inheritance.
Step-by-Step Strategy
- Implement Shape:
- Add name as instance attribute (e.g., set in init).
- Define area(self) returning 0.
- Override str(self) to format as "{name}: area={self.area()}" (calls area() dynamically).
- Create Rectangle(Shape):
- init(self, width, height): Store dimensions, call super().init() with name="Rectangle".
- Override area(self): return width * height.
- Create Circle(Shape):
- init(self, radius): Store radius, call super().init() with name="Circle".
- Override area(self): return math.pi * radius ** 2 (import math).
- Test polymorphism: Instantiate objects and print—str invokes overridden area() automatically.
Common Pitfalls
- Forgetting super().init(): Leads to uninitialized base attributes like name.
- str calling area() incorrectly: Use self.area() (not hardcoded) to ensure polymorphism works.
- Missing math import for π: Area will error; format output to ~2 decimals if needed (e.g., "{:.2f}".format()).
- Hardcoding values: Avoid fixed names/dimensions—use parameters; don't redefine base methods unnecessarily.
- Attribute access: Store self.width, self.height, etc., for area(); scope issues if using locals.
Time & Space Complexity
- Time: Each area() is O(1) (constant arithmetic). str is O(1). Instantiation/construction is O(1).
- Space: O(1) per object (fixed attributes: name, dimensions). Hierarchy adds no asymptotic overhead; Python objects have minor constant overhead for MRO.