Polymorphism
On this page
Built-in ExampleUnrelated ClassesOverridden MethodsSwappable Behavior with PoliciesCoding Challenge 1 - Class PolymorphismCoding Challenge 2 – Polymorphism with Inheritance (Shapes)Polymorphism lets a caller use the same operation with different kinds of object. Each object supplies the behavior needed for that operation.
The UML class boxes show three separate classes with the same public operation. No inheritance line is drawn: these classes need not share a base to support status().
Built-in Example
print(len("Hello")) # counts characters
print(len(["a", "b", "c"])) # counts list items
print(len({"a": 1, "b": 2})) # counts dictionary pairsOutput
5
3
2
len counts characters, list elements or dictionary entries according to the object.
Unrelated Classes
class StudentUser:
def status(self):
print("Attends classes.")
class AdminUser:
def status(self):
print("Manages the system.")
class GuestUser:
def status(self):
print("Limited access.")Defines StudentUser, AdminUser, GuestUser; no visible output.
All three classes provide status. They need no common base class for this use.
users = [StudentUser(), AdminUser(), GuestUser()]
for u in users:
u.status()Output
Attends classes.
Manages the system.
Limited access.
The loop calls the same method; each object prints its own response.
Overridden Methods
Run these definitions in order:
class User:
def __init__(self, user_id, name, email):
self.user_id = str(user_id)
self.name = name
self.email = email
def role(self):
return "user"Defines User; no visible output.
class Student(User):
def __init__(self, user_id, name, email):
super().__init__(user_id, name, email)
def role(self):
return "student"Defines Student; no visible output.
class Professor(User):
def __init__(self, user_id, name, email):
super().__init__(user_id, name, email)
self.teaches = []
def role(self):
return "professor"Defines Professor; no visible output.
u1 = Student(101, "Anna", "anna@example.com")
u2 = Professor(44, "Dr. Singh", "singh@example.com")
for u in (u1, u2):
print(u.role())Output
student
professor
The caller uses role() without checking whether the object is a student or professor.
Swappable Behavior with Policies
A policy is an object to which another object delegates a decision. First run the Schedule and Course definitions from Composition. The following classes reuse Course and provide two compatible format(course) operations.
The hollow triangle marks inheritance from Course; dashed arrows show possible dependencies on formatting policies. The two policy classes provide the same operation without inheriting from one another.
class SummaryPolicy:
def format(self, course):
return f"{course.code} — {course.title} ({course.credits} cr)"
class SummaryWithSchedule:
def format(self, course):
return f"{course.code} — {course.title} ({course.credits} cr) [{course.schedule}]"
class CourseWithPolicy(Course):
def __init__(self, code, title, credits, schedule=None, policy=None):
super().__init__(code, title, credits, schedule)
self.policy = policy or SummaryPolicy() # composed behavior
def summary(self):
return self.policy.format(self)Defines SummaryPolicy, SummaryWithSchedule, CourseWithPolicy; no visible output.
CourseWithPolicy inherits Course’s data handling but delegates summary formatting to its policy.
plain = CourseWithPolicy("NLP220", "Natural Language Processing", 6)
fancy = CourseWithPolicy("NLP220", "Natural Language Processing", 6,
policy=SummaryWithSchedule())
plain.add_meeting("Thu", "10:00-11:30")
fancy.add_meeting("Thu", "10:00-11:30")
print(plain.summary())
print(fancy.summary())Output
NLP220 — Natural Language Processing (6 cr)
NLP220 — Natural Language Processing (6 cr) [Thu 10:00-11:30]
The plain policy omits meeting information; the other includes it. Matching names alone are insufficient: both policies must also accept the expected arguments and return suitable text.
Coding Challenge 1 - Class Polymorphism
Complete three handlers: print to the console, append to self.messages, and simulate remote delivery by printing. In process_logs, loop over handlers first, then messages. Call handle(message) without type checks.
Exercise starter: complete before running.
# Coding Challenge 1: Class Polymorphism (Strategy-style Logging)
class ConsoleHandler:
def handle(self, message):
# TODO: print message to console
pass
class MemoryHandler:
def __init__(self):
# TODO: initialize self.messages to an empty list
pass
def handle(self, message):
# TODO: append to internal list
pass
class RemoteHandler:
def handle(self, message):
# TODO: "send" message to a remote server (simulate with print)
pass
def process_logs(handlers, messages):
# TODO: outer loop over handlers, inner loop over messages; call handle(message)
passExercise starter: complete before running.
handlers = [ConsoleHandler(), MemoryHandler(), RemoteHandler()]
messages = ["User logged in", "Error: invalid password"]
process_logs(handlers, messages)
print("Stored messages:", handlers[1].messages)Target output for your completed solution
[Console] User logged in
[Console] Error: invalid password
[Remote] Sending: User logged in
[Remote] Sending: Error: invalid password
Stored messages: ['User logged in', 'Error: invalid password']
Coding Challenge 2 – Polymorphism with Inheritance (Shapes)
Implement area() for Circle, Rectangle and Triangle. Require non-negative dimensions. Make total_area sum the returned areas without inspecting concrete types.
Exercise starter: complete before running.
# Coding Challenge 2: Polymorphism with Inheritance (Shapes)
import math
class Shape:
def area(self):
# TODO: subclass should override
raise NotImplementedError("Subclasses must implement area()")
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
# TODO: implement area()
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
# TODO: implement area()
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
# TODO: implement area()
def total_area(shapes):
# TODO: sum up areas of all shapes in the list
pass
# demo
shapes = [
Circle(3), Rectangle(6, 5), Triangle(4, 8)
]
print("Total area:", round(total_area(shapes), 2))Target output for your completed solution
Total area: 74.27
The fixture has areas 9 * math.pi, 30 and 16. NotImplementedError reports a missing operation when called; it does not make Shape an abstract base class. Interfaces and behavioral contracts, discussed in the next chapter, make these expectations explicit.