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.
1) foo(
foo
1.0) foo(
It's a float
"1") foo(
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.registry
mappingproxy({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()