In this post, code flexibility refers to how much existing code needs to be changed when new requirements arise. A flexible piece of code is one where new needs can be addressed simply by adding new code — without having to modify what’s already there. In software design, this is known as the Open/Closed Principle, which states that well-designed code should be open to extension but closed to modification.
Why is this important? Because once a piece of code has been thoroughly tested and proven to work, modifying it introduces a risk of bugs — and that means retesting, which can be expensive and time-consuming in a large codebase.
Example: Modeling a Recycling Plant
Let’s say we’re building a model of a recycling plant. Initially, the plant only processes metal waste. So, in our model, we define a MetalWaste class and a RecyclingPlant class with a process() method like this:
|
1 2 3 4 5 6 7 |
class MetalWaste:... class RecyclingPlant: def process(self, waste: MetalWaste):... |
Later, the plant gains the ability to process plastic waste as well.
The question is: how should we reflect this new capability in our class design?
Option 1: Use Type Checks Inside the Method
One solution is to modify the existing process() method to accept both metal and plastic, then use if statements or type checks to handle each case accordingly. However, this approach violates the Open/Closed Principle. Each new waste type would require editing the method body, adding more conditional branches.
Option 2: Use Separate Methods for Each Type
Another idea is to define a separate method for each type of waste:
|
1 2 3 4 5 6 7 8 9 |
class MetalWaste:... class PlasticWaste:... class RecyclingPlant: def process_metal_waste(self, waste: MetalWaste):... def process_plastic_waste(self, waste: PlasticWaste):... |
This keeps the code extensible, but it introduces another issue: the calling code must also change depending on the waste type. That becomes problematic if the calling code is outside your control or reused in multiple places.
Can’t We Just Overload Methods in Python?
You might think of using method overloading, defining multiple methods with the same name but different argument types — like in some statically typed languages. But in Python, if you define multiple methods with the same name, only the last definition takes effect. Any previous ones are silently overridden.
So, what can we do?
The Solution: singledispatchmethod
Fortunately, Python offers a neat solution via the functools module: the singledispatchmethod decorator (and its function counterpart, singledispatch). This lets you define one generic method and then register different implementations for different argument types — just like overloading.
Here’s how it works using our recycling plant example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from functools import singledispatchmethod # Waste types class MetalWaste: ... class PlasticWaste: ... class RecyclingPlant: @singledispatchmethod def process(self, waste): raise NotImplementedError('This type of waste cannot be processed.') @process.register(MetalWaste) def _(self, waste): print('Processing metal waste...') @process.register(PlasticWaste) def _(self, waste): print('Processing plastic waste...') # TEST plant = RecyclingPlant() waste_items = (MetalWaste(), PlasticWaste()) # Generic processing code, independent of the waste types for item in waste_items: plant.process(item) |
Output:
|
1 2 3 4 |
Processing metal waste... Processing plastic waste... |
What Did We Achieve?
Using this method, we can easily add and process new waste types by:
- Creating a new waste class
- Adding a new @process.register(NewType) method to the RecyclingPlant class
All this without changing the existing process() call in the rest of the program. That’s real flexibility, and it follows the Open/Closed Principle in practice.
This means that in Python, we can achieve function or method overloading behavior based on the type of the first argument, thanks to singledispatch and singledispatchmethod.
For a deeper dive into this topic, check out the chapter “Multiple functions or methods with the same name but different parameter types” in the e-book Python Knowledge Building Step by Step.