Generic functions with Python's singledispatch

Redowan Delowar April 5, 2020
Source

Updated on 2022-02-13: Change import style of functools.singledispatch.

Recently, I was refactoring a portion of a Python function that somewhat looked like this:

This pattern gets tedious when the number of conditions and actionable functions start to grow. I was looking for a functional approach to avoid defining and calling three different functions that do very similar things. Situations like this is where parametric polymorphism comes into play. The idea is, you have to define a single function that'll be dynamically overloaded with alternative implementations based on the type of the function arguments.

Function overloading & generic functions

Function overloading is a specific type of polymorphism where multiple functions can have the same name with different implementations. Calling an overloaded function will run a specific implementation of that function based on some prior conditions or appropriate context of the call.

When function overloading happens based on its argument types, the resulting function is known as generic function. Let's see how Python's singledispatch decorator can help to design generic functions and refactor the icky code above.

Singledispatch

Python fairly recently added partial support for function overloading in Python 3.4. They did this by adding a neat little decorator to the functools module called singledispatch. In Python 3.8, there is another decorator for methods called singledispatchmethod. This decorator will transform your regular function into a single dispatch generic function.

A generic function is composed of multiple functions implementing the same operation for different types. Which implementation should be used during a call is determined by the dispatch algorithm. When the implementation is chosen based on the type of a single argument, this is known as single dispatch.

As PEP-443 said, singledispatch only happens based on the first argument's type. Let's take a look at an example to see how this works!

Example-1: Singledispatch with built-in argument type

Let's consider the following code:

Running this code will return:

The above code snippet applies process_int or process_float functions on the incoming number based on its type. Now let's see how the same thing can be achieved with singledispatch:

Running this will return the same result as before.

Example-2: Singledispatch with custom argument type

Suppose, you want to dispatch your function based on custom argument type where the type will be deduced from data. Consider this example:

Running this snippet will print out:

To refactor this with singledispatch, you can create two data types Cat and Dog. When you make Cat and Dog objects from the classes and pass them through the process function, singledispatch will take care of dispatching the appropriate implementation of that function.

Running this will print out the same output as before:

Further reading

Discussion in the ATmosphere

Loading comments...