from fastai2.vision.all import *
Let's say we want to create a function that does floor division //
if the input is a int
and normal division /
if the input is a float
.
One way of doing that is a function that checks (with if
s conditions) the input type and executes the appropriate operation.
def same_type_div(a, b):
if type(a) is int: return a//b
if type(a) is float: return a/b
same_type_div(5, 2), same_type_div(5., 2)
Alright, works well enough.
Let's say after some time we want to include complex numbers, let's say we want our users to give the complex numbers as strings and convert to complex
internally. We now have to change the original function to the following:
def same_type_div(a, b):
if type(a) is int: return a//b
if type(a) is float: return a/b
if type(a) is str: return complex(a)/complex(b)
same_type_div('1+1j', '1j')
Simple enough right? But what happens when you can't modify the source code? (I mean, if the package is open source, you can always go to your local installation and modify the lines that you want, but good luck maintaining that updated. We should be able to modify the function behaviour without having to change the source code directly).
typedispatch
comes to our rescue, let's say the library you're using handles int
and float
:
@typedispatch
def same_type_div2(a:int, b): return a//b
@typedispatch
def same_type_div2(a:float, b): return a/b
If you're used with standard Python you may be a bit confused with what just happened in the two cells above, we declared the same function twice! Your intuition must be saying "the latter is going to overwrite the former!".
Well, typedispatch
will keep both versions for us, and when we call the function, it will dispatch the one that if finds more appropriate, let's check that:
same_type_div2(5, 2), same_type_div2(5., 2)
Now, adding your custom implementation for complex numbers is simple as:
@typedispatch
def same_type_div2(a:str, b): return complex(a)/complex(b)
same_type_div2('1+1j', '1j')
By using typedispatch
we don't need to touch the source code!
We can be even more specific and define what happens depending on the type of b
!
Let's create a function that return the modulus of the division if a
is a str
and b
an int
.
@typedispatch
def same_type_div2(a:str, b:int): return abs(complex(a)/b)
same_type_div2('1+1j', 2)
And there is more, it also works with subclasses:
class MyInt(int): pass
same_type_div2('1+1j', MyInt(2))
It correctly finds the implementation for int
and uses that. But if we define a implementation for MyInt
directly:
@typedispatch
def same_type_div2(a:str, b:MyInt): return complex(a)/b
same_type_div2('1+1j', MyInt(2))
It starts using that! typedispatch
will always try to use the "closest" implementation it can find for your type.