Python Type Hints and Polymorphism
No. 37
Type hints were introduced in Python 3.5 so you can write functions with the expected argument and return types specified:
def adder(x: float, y: float) -> float:
However, the Python compiler does not enforce these type declarations. Instead, your IDE (such as PyCharm or Visual Studio) can flag such type disagreements in case you want to fix them. For example, you might use this adder function in a little program like this:
def adder(x: float, y: float) -> float:
sumRes: float = x + y
return sumRes
result: float = adder(2.1, "3")
print(result)
Note that the call to the adder function contains not two floats, but an integer and a string. This is clearly an error and if you run this program, you will get the error:
unsupported operand type(s) for +: 'int' and 'str'
Python resolves typing at run time, not at compile time, and Python variables can take on different types such as float, int or sting, or list, dict or set for that matter. It is only when you pass a variable to some function or method that the type gets checked, and by then your program is running (or not!).
IDE type checking
But, if you paste this code into PyCharm or Visual Studio’s Integrated Development Environment (IDE), you will find under Problems, the message
Expected type 'float', got 'str' instead
without even having to run the program.
What about Polymorphism?
But supposing you want to have functions that operate on several kinds or arguments. A simple example might be one add method for (float, float) and one for (float, string)
def adder(x:float, s:str)->float:
sumRes: float = x + float(s)
return sumResdef adder(x: float, y: float) -> float:
sumRes: float = x + y
return sumRes
In Python alone, this won’t work. The second adder method overwrites the first during compilation. This particular polymorphism is also called overloading.
But there are some Python decorators that come close to what you need.
Let’s start by creating an Adder class where one value is entered in the initializer, and the other in the add method. This may seem odd, but it is a way to write the simplest example:
class Adder:
# save first value in the initializer
def __init__(self, aval: int):
self.aval = aval # save as instance variable
# second value entered in add method
def add(self, bval:int)->float:
self.bval=bval # save 2nd as instance variable
return self.aval+self.bval #return sum
addr = Adder(20) # create the Adder class
theSum = addr.add(30) # call add with 2nd argument- sum returned
print(theSum)
The singledispatch method
Now it would be nice to have ways to create other initializers and this is what the decorator @singledispatchmethod does.
You import this method from the functools library by:
from functools import singledispatchmethod
Then you can overload functions with a single argument by labeling the first with the decorator:
@singledispatchmethod
def __init__(self, aval: float): # init with a float
self.aval = aval
self.bval = 0
Then you create an additional overloaded init function where the argument is a string:
@__init__.register(str) # init with a stringdef _from_str(self, str):
self.aval = float(str)
Note, however, that this decorator only works for a single argument to a method, and there cannot be additional arguments.
You can create overloaded add methods in the same way, though:
@singledispatchmethoddef add(self, bval: float)->float: # add a float
self.bval=bval
return self.aval+self.bval@add.registerdef _(self, sval: str)->float: # add a string
self.bval = float(sval)
return self.aval + self.bval
Now we can call initializers or add methods with floats or strings and and the correct method will be selected:
addr = Adder(20)
theSum = addr.add(30)
print(theSum)
addr = Adder("21")
theSum = addr.add(30)
print(theSum)
addr = Adder("22")
theSum = addr.add("32")
print(theSum)
The multimethod library
Now, as we noted, this only works when the methods have a single argument, and the syntax is a little clunky. To overload functions with multiple arguments we use the multimethod function in the pypi.org library , which is not part of Python’s release but easily installed with pip. Or, in fact, PyCharm can find and install it for you. You import it as follows:
from multimethod import multimeta
Then you declare your Adder class with a special metaclass option:
class Add(metaclass=multimeta):
# float, float
def add(self, aval: float, bval: float) -> float:
return aval + bval
# string, float
def add(self, sval: str, bval: float) -> float:
return float(sval) + bval
These represent two of four possible combinations. The other two are quite analogous. Note how clean and simple this it.
Then you can call the overloaded add methods like this:
adder = Add()
theSum = adder.add(33.2,31.2)
print(f'{theSum:5.2f}')
theSum = adder.add('44.2', 23.2)
print(f'{theSum:5.2f}')
Overloading individual functions
This multimeta approach only works for classes, which is probably the greatest use of this library. However, it is possible to create overloaded stand along functions as well, using this same multimethod library as we see here:
from multimethod import multimethod# declare the float, float version@multimethoddef add(aval:float, bval:float)->float:
return aval + bval# declare the string, float version@multimethoddef add(sval: str, bval: float)->float:
return float(sval)+ bval# call the float, float versiontheSum = add(22.1, 33.2)
print(f'{theSum:5.2f}')# call the string, float versiontheSum = add('32.2', 33.1)
print(f'{theSum:5.2f}')
Summary
Python is a dynamically typed language, where data types are checked only at run time. The compiler does not check for type consistency, but you can still declare types so that the IDE can check them in advance of running the program.
Functions and methods inside classes thus do not have specific argument types and whatever value you pass into these functions must make sense to the function. However with type hints the IDE can check for this consistency. But for this reason Python does not support polymorphism or method overloading. However, you can use various libraries such as functools and multimethod to introduce this polymorphism.
All of the source code can be found in GitHub under jameswcooper/newsletter/Typehints1
Code includes:
· typehints.py -- example of using type hints
· adder.py – a simple Adder class
· singledispatch.py – using the singledispatchmethod in functools
· mmeta.py – using the metaclass to create overloading class methods
· multimethod.py – overloading functions outside of a class
· multidisp.py – Using the multipledispatch library to overload functions outside a class
