Why do I need singledispatch?
Other programming languages have native method or function overloading. For example, in Java one can make a class with methods that run different code depending on the datatype of the input.
With singledispatch
one can run a different function when running my_func(<int>)
vs my_func(<str>)
.
The following example should help explain.
Note: singledispatch
was only added in Python 3.4. Another reason to migrate!
Example without singledispatch
One common example I run into where data types matter is number formatting. Usually product unit sales are represented as integers and with revenue as a float.
If I want to print currency figures on only the float values I would need to do something like this:
def print_currency(val):
if isinstance(val, float):
return '$' + str(val)
else:
return str(val)
units = 10
revenue = 100.0
output = f'We sold {print_currency(units)} units for {print_currency(revenue)}.'
print(output)
>>> 'We sold 10 units for $100.0.'
Example with singledispatch
Note: The "dispatch" only happens on the first argument (hence, "single" dispatch, applying to more arguments would be "multiple" dispatch [Fluent Python by Luciano Ramalho]),
from functools import singledispatch
@singledispatch
def print_currency(arg):
pass
@print_currency.register(float)
def _(val):
return '$' + str(val)
@print_currency.register(int)
def _(val):
return str(val)
units = 10
revenue = 100.0
output = f'We sold {print_currency(units)} units for {print_currency(revenue)}.'
print(output)
>>> 'We sold 10 units for $100.0.'
Newer example
In Python 3.7 singledispatch
was updated to allow for type annotations in function arguments. Therefore you don't need to use a "type" in the register
function.
from functools import singledispatch
@singledispatch
def print_currency(arg):
pass
@print_currency.register
def _(val:float):
return '$' + str(val)
@print_currency.register
def _(val:int):
return str(val)
units = 10
revenue = 100.0
output = f'We sold {print_currency(units)} units for {print_currency(revenue)}.'
>>> 'We sold 10 units for $100.0.'
Conclusion
While the above examples don't seem so necessary, since the original if/else may be sufficient, I feel the code looks a lot more clear when having different functions for different types as opposed to many chains of if/else which can easily get unweildy. I think Ramalho puts it well in Fluent Python:
@singledispatch is not designed to bring Java-style method overloading to Python. A single class with many overloaded variations of a method is better than a single function with a lengthy stretch of if/elif/elif/elif blocks. But both solutions are flawed because they concentrate too much responsibility in a single code unit the class or the function. The advantage of @singledispath is supporting modular extension: each module can register a specialized function for each type it supports. Ramalho, Luciano. Fluent Python: Clear, Concise, and Effective Programming (p. 211)
By using singledispatch
in your code you can make your code more readable and modular if you require a concept similar to function overloading.
Comments