Polymorphism with Single Dispatch in Python
Python does not natively support function overloading, a feature commonly used in languages like C++ to create different behaviors based on function signatures. However, in Python, there is a way to achieve a level of polymorphism although in an extremely limited scope.
For functions that accept a single argument, functools.singledispatch()
offers a straightforward means of registering dispatch functions according to the type of the single argument. The following code snippet illustrates a basic example of this feature:
import functools
from typing import *
@functools.singledispatch
def f(x: Any) -> str:
return f"type: {type(x)}, value: {x}"
@f.register
def _(x: int) -> str:
return f"int: {x}"
@f.register
def _(x: list) -> str:
return f"list: {x}"
print(f(3)) # int: 3
print(f([4, 5])) # list: [4, 5]
print(f("foo")) # type: <class 'str'>, value: foo
Here, the original function f(x)
is old-school Python, accepting an object of any type. Traditionally, branching based on the type of x
might appear as:
def f(x: Any) -> str:
if isinstance(x, int):
...
elif isinstance(x, list):
...
else: # Any
...
This approach lacks elegance, concealing the different behavior for each input type within the implementation; we have to look into the code to know it. In contrast, using the functools.singledispatch
decorator clearly exposes the distinct behaviors based on input types.
Needless to say, functools.singledispatch()
does not provide full polymorphism in Python. However, for utility library functions that handle a single object at a time, it offers an elegant approach to streamline our code.