Designing a Shape Hierarchy with Abstract Base Classes
This challenge focuses on understanding and implementing Abstract Base Classes (ABCs) in Python. ABCs are crucial for defining interfaces and ensuring that subclasses adhere to a specific contract, promoting code reusability and maintainability in object-oriented design. You will create a system for geometric shapes where common behaviors are enforced through an abstract base class.
Problem Description
Your task is to design a system for different geometric shapes (e.g., Circle, Rectangle, Triangle) using Python's abc module. You need to create an abstract base class, Shape, that defines a common interface for all shapes. Specifically, Shape must declare abstract methods for calculating its area and perimeter.
Key Requirements:
-
Abstract Base Class
Shape:- Must inherit from
abc.ABC. - Must define an abstract method
area()that returns a floating-point number representing the area of the shape. - Must define an abstract method
perimeter()that returns a floating-point number representing the perimeter of the shape. - Should not provide any implementation for
area()orperimeter().
- Must inherit from
-
Concrete Shape Classes:
- Create at least three concrete shape classes:
Circle,Rectangle, andTriangle. - Each concrete class must inherit from
Shape. - Each concrete class must implement both the
area()andperimeter()methods according to its specific geometric formulas. - Instantiating a concrete shape should be done by passing relevant dimensions (e.g., radius for Circle, width and height for Rectangle, three side lengths for Triangle).
- Create at least three concrete shape classes:
Expected Behavior:
- Attempting to instantiate
Shapedirectly should raise aTypeError. - Attempting to instantiate a concrete shape class without implementing all abstract methods should raise a
TypeError. - Instantiating a concrete shape class and calling its
area()andperimeter()methods should return the correct calculated values.
Edge Cases to Consider:
- For
Triangle, consider the triangle inequality theorem (the sum of any two sides must be greater than the third side). While not strictly required to prevent invalid triangles in this challenge, be mindful of how your calculations might behave. - Ensure floating-point precision is handled appropriately for calculations.
Examples
Example 1:
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
if radius < 0:
raise ValueError("Radius cannot be negative")
self.radius = radius
def area(self):
return math.pi * self.radius**2
def perimeter(self):
return 2 * math.pi * self.radius
# --- Usage ---
my_circle = Circle(5)
print(f"Circle Area: {my_circle.area()}")
print(f"Circle Perimeter: {my_circle.perimeter()}")
Circle Area: 78.53981633974483
Circle Perimeter: 31.41592653589793
Explanation: A Circle object is created with a radius of 5. Its area() method calculates π * r², and its perimeter() method calculates 2 * π * r.
Example 2:
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
if width < 0 or height < 0:
raise ValueError("Width and height cannot be negative")
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# --- Usage ---
my_rectangle = Rectangle(10, 5)
print(f"Rectangle Area: {my_rectangle.area()}")
print(f"Rectangle Perimeter: {my_rectangle.perimeter()}")
Rectangle Area: 50
Rectangle Perimeter: 30
Explanation: A Rectangle object is created with a width of 10 and a height of 5. Its area() method calculates width * height, and its perimeter() method calculates 2 * (width + height).
Example 3:
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Triangle(Shape):
def __init__(self, side1, side2, side3):
if any(s <= 0 for s in [side1, side2, side3]):
raise ValueError("Sides must be positive")
# Basic check for triangle inequality
if not (side1 + side2 > side3 and side1 + side3 > side2 and side2 + side3 > side1):
print("Warning: These sides do not form a valid triangle.")
self.a, self.b, self.c = side1, side2, side3
def area(self):
# Heron's formula
s = (self.a + self.b + self.c) / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
# --- Usage ---
my_triangle = Triangle(3, 4, 5) # A right-angled triangle
print(f"Triangle Area: {my_triangle.area()}")
print(f"Triangle Perimeter: {my_triangle.perimeter()}")
# Demonstrating instantiation error for incomplete ABC
try:
class IncompleteShape(Shape):
def area(self):
return 10
IncompleteShape()
except TypeError as e:
print(f"Error when creating incomplete shape: {e}")
Triangle Area: 6.0
Triangle Perimeter: 12
Error when creating incomplete shape: Can't instantiate abstract class IncompleteShape with abstract methods perimeter
Explanation: A Triangle object is created with sides 3, 4, and 5. Its area() uses Heron's formula, and its perimeter() sums the sides. The example also shows how Python enforces the abstract method contract by raising a TypeError if a subclass doesn't implement all abstract methods.
Constraints
- All calculations for area and perimeter should be performed using floating-point numbers.
- Input dimensions for shapes (radius, width, height, side lengths) will be non-negative numbers.
- Your solution should not use any external libraries beyond Python's standard library, specifically the
abcmodule andmathforpiandsqrt.
Notes
- Remember that abstract methods declared in an ABC cannot be called directly. They are meant to be implemented by concrete subclasses.
- The
abcmodule provides the@abstractmethoddecorator to designate methods as abstract. - Consider how you would extend this system to include other shapes like Squares, Ellipses, etc., and how the ABC design facilitates this.
- For the
Triangleclass, you can assume valid inputs for sides that form a triangle, or add basic validation as shown in Example 3. The core of the challenge is the ABC structure.