from functools import singledispatch
@singledispatch
def foo(x):
print("foo")With Python>=3.7, the @singledispatch method can now understand the type hints. It behaves like function overloading but it’s more dynamic than the static langauge.
Here is a quick example to demonstrate it.
@foo.register
def _(x: float):
print("It's a float")
@foo.register
def _(x: str):
print("It's a string now!")Let’s see how it works.
foo(1)foo
foo(1.0)It's a float
foo("1")It's a string now!
The function foo now understand the type of the argument and dispatch the corresponding functions. This is nicer than a big chunk of if/else statement since it’s less couple. It’s also easy to extend this. Imagine the foo function is import from a package, it’s easy to extend it.
# Imagine `foo` was imported from a package
# Now that you have a special type and you want to extend it from your own library, you don't need to touch the source code at all.
# from bar import foo
class Nok:
...
@foo.register
def _(x: Nok):
print("Nok")
nok = Nok()
foo(nok)Nok
This is only possible because Python is a dynamic language. In contrast, to achieve the same functionalities with monkey patching, you would need to copy the source code of the function and extend the if/else block.
Let’s dive a bit deeper to the decorator.
print([attr for attr in dir(foo) if not attr.startswith("_")])['dispatch', 'register', 'registry']
foo.dispatch<function functools.singledispatch.<locals>.dispatch(cls)>
foo.register<function functools.singledispatch.<locals>.register(cls, func=None)>
foo.registrymappingproxy({object: <function __main__.foo(x)>,
float: <function __main__._(x: float)>,
str: <function __main__._(x: str)>,
__main__.Nok: <function __main__._(x: __main__.Nok)>,
__main__.Nok: <function __main__._(x: __main__.Nok)>})
from collections import abc
isinstance(foo.registry, abc.Mapping)The foo.registry is the most interesting part. Basically, it’s a dictionary-like object which store the types. It behaves like
if type(x) == "int":
do_something()
elif type(x) == "float":
do_somthing_else()
else:
do_this_instead()