What is Singledispatch descriptor in Functools module in Python?

Overview

In Python, we use the functools module to create higher-order functions that interact with other functions. The higher-order functions either return other functions or operate on them to broaden their scope without modifying or explicitly defining them.

The singledispatch decorator

The singledispatch decorator is used for function overloading. The decorator turns a function into a generic function that can have different implementations depending on the function’s first argument.

Type annotation for function overloading

We can use type annotation for the first function argument depending on which function implementation is chosen. The decorator will infer the type of the first argument automatically.

Example

Refer to the example code below:

from functools import singledispatch
@singledispatch
def func(arg1, arg2):
print("default implementation of func - ", arg1, arg2)
@func.register
def func_impl_1(arg1: str, arg2):
print("Implementation of func with first argument as string - ", arg1, arg2)
@func.register
def func_impl_2(arg1: int, arg2):
print("Implementation of func with first argument as string - ", arg1, arg2)
func(1, "hello")
func("test", "hello")
func(1.34, "hi")

Explanation

  • Line 1: We import the singledispatch from the functools module.

  • Line 3–5: We define a function named func to accept two arguments, arg1 and arg2. The func is decorated with a singledispatch decorator. This is the default implementation of the function func.

  • Line 7-9: We register the first implementation of func for which arg1 shall be of type string. Here, arg1 is annotated with string as its type.

  • Line 11-13: We register the second implementation of func for which arg1 shall be of type int. Here, arg1 is annotated with int as its type.

  • Line 15-17: We invoke func with different values for arg1.

  • Line 17: We execute the default implementation of the function as there is no implementation of func for the float type.

The register attribute for function overloading

If the code doesn’t use type annotations, the relevant type argument can be passed explicitly to the decorator via register.

Example

Refer to the example code below:

from functools import singledispatch
@singledispatch
def func(arg1, arg2):
print("default implementation of func - ", arg1, arg2)
@func.register(str)
def func_impl_1(arg1, arg2):
print("Implementation of func with first argument as string - ", arg1, arg2)
@func.register(int)
def func_impl_2(arg1, arg2):
print("Implementation of func with first argument as int - ", arg1, arg2)
func(1, "hello")
func("test", "hello")
func(1.34, "hi")

Explanation

  • Line 1: We’ll import the singledispatch from the functools module.

  • Line 3-5: A function named func is defined that accepts two arguments, arg1 and arg2. The func is decorated with a singledispatch decorator. This is the default implementation of the function func.

  • Line 7–9: We register the first implementation of func for which arg1 should be of type string. This is done by providing str to the register attribute.

  • Line 11-13: We register the second implementation of func for which arg1 should be of type int. This is done by providing int to the register attribute.

  • Lines 15-17: We invoke func with different values for arg1.

  • Line 17: We execute the default implementation of the function as there is no implementation of func for the float type.

Decorator stacking

We specify multiple types with the exact implementation by registering the function with different types numerous times.

  • Line 11-14: We register multiple types for the exact implementation of func.

  • Lines 15-17: We invoke func with different values for arg1.

from functools import singledispatch
@singledispatch
def func(arg1, arg2):
print("default implementation of func - ", arg1, arg2)
@func.register(str)
def func_impl_1(arg1, arg2):
print("Implementation of func with first argument as string - ", arg1, arg2)
@func.register(int)
@func.register(float)
def func_impl_2(arg1, arg2):
print("Implementation of func with first argument as integer or float - ", arg1, arg2)
func(1, "hello")
func("test", "hello")
func(1.34, "hi")

Get all implementations of a function

We obtain all the different implementations registered to a function using the registry attribute attached. It’s a dictionary that stores the various implementations for other types.

  • Line 11-14: We register multiple types for the exact implementation of func.

  • Line 17: We print the different implementations and types registered for func using the registry attribute.

  • Line 20: We obtain the str implementation of func as registry is a dictionary.

from functools import singledispatch
@singledispatch
def func(arg1, arg2):
print("default implementation of func - ", arg1, arg2)
@func.register(str)
def func_impl_1(arg1, arg2):
print("Implementation of func with first argument as string - ", arg1, arg2)
@func.register(int)
@func.register(float)
def func_impl_2(arg1, arg2):
print("Implementation of func with first argument as integer or float - ", arg1, arg2)
print("The different implementations of the func are")
print(func.registry)
print("String implementation:")
print(func.registry[str])

Free Resources