Polymorphism in Python
Polymorphism in Python
No. 36
Polymorphism just means “many shapes,” and in the context of software, it means that function calls or method calls with the same name do different things in different situations.
To take one of the simplest examples, Let’s consider the Python len function, which returns the length of quite a few different sorts of objects. The simplest are the lengths of strings, lists, sets and tuples. Even though they are rather different internally, they all have a length that you can obtain with the len functions:
name = 'Snuffy Smith'mylist = [1,4,6,4,8,7]
myset= {'apples', 'oranges', 'grapes'}
mytuple = (23, 42, 76, 143)
print(len(name), len(mylist), len(myset), len(mytuple))
which of course produces the result:
12 6 3 4
In fact, that len function will operate on any object that has a __len__ method within. So, lets create an Employee class where we keep each employee’s height. Note that for brevity, we are using the @dataclass decorator.
@dataclassclass Employee:
frname: str
lname: str
salary: int
height: int
def compensation(self):
return self.salary
def __len__(self):
return self.height
Now, we can create an instance for old Snuffy, and use the len function to get his height. If you remember the comic strip, he spent much of his time lying down, so this is appropriate:
emp =Employee('Snuffy', 'Smith', 1200, 67))
print (emp.frname, len(emp)
This prints out
Snuffy 67
as expected.
Polymorphism in derived classes
We can also create two classes where one of the methods does something different in one class than in the other. In this case, one is a derived class, but they could be independent. We first create an Employee class with a compensation method which just returns that employee’s salary.
class Employee:
frname: str
lname: str
salary: int
def compensation(self):
return self.salary
But, we might have special employees who get stock options, and we want to handle that in a new TopEmployee class:
class TopEmployee(Employee):
def __init__(self, frname, lname, salary):
super().__init__(frname, lname, salary)
self.stockOptions = 0
def setOptions (self, stockval):
self.stockOptions = stockval
def compensation(self):
return self.salary + self.stockOptions
Now, if we create one of each kind of employee, we see that the compensation for the TopEmployee includes his stock options:
emp = Employee('Snuffy', 'Smith', 11000)
topEmp = TopEmployee('James', 'Bond', 100000)
topEmp.setOptions(5000)
print(emp.frname, emp.compensation())
print (topEmp.frname, topEmp.compensation())
So, the compensation method is polymorphic and returns more information in the TopEmployee case.
Adding numbers of different types
In a similar fashion, you could create two classes with polymorphic addNum methods for adding numbers or strings:
class Summerf():
def addNums(self, x: float, y: float) ->float:
return x + yclass Summers():
def addNums(self, s1: str, s2: str) -> float:
fsum = float(s1) + float(s2)
return fsum
Then, you could get the right answer when some of the arguments are strings:
sumrf = Summerf()
sumrs = Summers()
print(sumrf.addNums(12.0, 2.3))
print(sumrs.addNums(22.3, "13.5"))
Note that the second call to sumrs.addNums works because float(float) is still a floating point number.
Polymorphic methods within a single class
Rather than creating two classes, why not create a single class with two different addNum methods?
class Summer():
def addNums(self, x: float, y: float) ->float:
return x + y
def addNums(self, f: float, s: str)->float:
fsum = f + float(s)
return fsum
This looks like it would work, and in some languages, you can have polymorphic methods like this in a single class, where they are distinguished by the data types of the arguments. This will not work in Python, however, because Python uses a single-pass compiler and because types are not resolved until runtime. The fact that you can declare type hints may help the IDE’s pre-processor, but it does not change the code. Python only recognizes the last occurrence of a method in a class: the first one is essentially overwritten. You can create cases where this might seem to work, but the first addNums method is never called.
If you want to do this sort of thing, you have to create both of the classes as we did just above or two methods with different names, and use a factory to decide which one to use, as follows:
class Summer():
def addNumsf(self, x: float, y: float) ->float:
return x + y
def addNums(self, f: float, s: str)->float:
if type(s) is float:
return self.addNumsf(f,s)
else:
fsum = f + float(s)
return fsum
Using a Factory pattern
Another way to do this is to write a little class that returns the correct addition class depending on whether the two arguments are numeric or not. It does this by converting each argument toa string and using the string’s isNumber method:
"""Decides which addition class to return"""
class WhichSum:
def __init__(self,x,y):
self.x = x
self.y = y
# if it is an integer or float result will be true
def isNumber(self, z):
return (str(z).isnumeric())
# return the numeric addition class if both are numbers
# otherwise return the mixed type addition class
def getSummer(self)->Summer:
if self.isNumber(self.x) \
and self.isNumber((self.y)):
return SummerNum(self.x, self.y)
else:
return SummerMix(self.x, self.y)
This is an example of the SimpleFactory Design Pattern. Here is how we call it:
sf = WhichSum(12.2, '13.4')
adder = sf.getSummer() # get the classprint(adder.add())
sf = WhichSum(123, 45.6)
print(sf.getSummer().add()) # use the class directly
Python Programming with Design Patterns
To learn more about Design Patterns, consult my new book Python Programming with Design Patterns. It came out in late December, and is now available on Amazon and Barnes and Noble. It’s published by Pearson.
All of the source code can be found in GitHub under jameswcooper/newsletter/Polymorphic
Code includes:
· Lentest.py -- illudtrates polymorphic use of the len function
· Employees.py – Employee and TopEmployee showing polymorphic compensation
· addClasses.py – creates two summer classes
· addNumsWrong – polymorphic calls that can’t work
· addNumsType.py – decides which addNum to call using type method
· addFactory.py – Uses WhichSum factory to return the correct addition class
