B3.2.2 Construct code to model polymorphism and its various forms, such as method overriding.
• The principle of polymorphism and how it contributes to code flexibility and reusability
• How to implement dynamic polymorphic behaviour through mechanisms like method overriding
• How to apply static polymorphic behaviour to maximize code efficiency
The big idea
Polymorphism means “many forms.”
In object-oriented programming, it allows different classes to be treated the same way through a common interface, even if they behave differently.
The benefit? We can write code that works with different object types without knowing their exact classes in advance — making programs flexible, reusable, and easier to extend.
Why polymorphism matters
- Flexibility: One block of code can handle many object types.
- Reusability: You can write general-purpose functions and methods.
- Extensibility: Adding new behavior often means just adding a new class that follows the existing pattern.
1. Dynamic polymorphism (runtime polymorphism)
In Python, dynamic polymorphism is achieved through method overriding — a subclass provides a specific implementation for a method already defined in its superclass.
Example:
class Animal:
def speak(self):
return "Some generic sound"
class Dog(Animal):
def speak(self):
return "Bark"
class Cat(Animal):
def speak(self):
return "Meow"
# Runtime decision
animals = [Dog(), Cat(), Animal()]
for animal in animals:
print(animal.speak())
Output:
Bark
Meow
Some generic sound
Key points:
- The
speak()method name is the same across all classes. - At runtime, Python decides which version to call based on the object type.
- This allows one loop to handle different animal types without type checks.
2. Static polymorphism (compile-time–style behavior in Python)
Python doesn’t have true compile-time polymorphism like Java or C++ (where method overloading is resolved at compile time). Instead, we can mimic static polymorphism by:
- Using default parameters
- Using
*argsand**kwargsto accept variable arguments - Type checking inside a single method
Example:
class MathUtils:
def add(self, a, b=0, c=0):
return a + b + c
math = MathUtils()
print(math.add(2, 3)) # Two parameters
print(math.add(2, 3, 4)) # Three parameters
Output:
5
9
Here, the same method name handles different numbers of arguments. The decision on how many values to sum is made in the function body, which Python processes at runtime — but the effect is similar to static polymorphism in other languages.
3. Combining both in a real scenario
Imagine a payment system:
- Dynamic polymorphism: Different payment types override a
process_payment()method. - Static polymorphism (Python-style): The same method name accepts different argument formats for convenience.
class Payment:
def process_payment(self, amount):
raise NotImplementedError
class CreditCardPayment(Payment):
def process_payment(self, amount):
return f"Processing credit card payment of ${amount}"
class PayPalPayment(Payment):
def process_payment(self, amount):
return f"Processing PayPal payment of ${amount}"
class PaymentProcessor:
def refund(self, amount, reason=None):
if reason:
return f"Refunded ${amount} due to: {reason}"
return f"Refunded ${amount}"
# Dynamic polymorphism
payments = [CreditCardPayment(), PayPalPayment()]
for p in payments:
print(p.process_payment(100))
# Static polymorphism effect
processor = PaymentProcessor()
print(processor.refund(50))
print(processor.refund(50, "Product defect"))
Best practices
- Use method overriding for dynamic polymorphism when subclass behavior differs.
- Use flexible parameter handling for Python’s style of static polymorphism.
- Keep method names consistent across related classes for maximum interchangeability.
- Avoid unnecessary type checks — rely on duck typing and clear method contracts.