def f(x:int) -> float: pass
= FastFunction(f).dispatch(f)
f f
f(int) -> float
fastdispatch
extends the wonderful plum
’s Julia-inspired implementation of multiple dispatch for Python.
FastFunction (f, owner=None)
Multiple dispatched function; extends plum.Function
It has a concise repr
:
It supports fastcore
’s backport of the |
operator on types:
def f1(x): return 'obj'
def f2(x:int|str): return 'int|str'
f = FastFunction(f1).dispatch(f1).dispatch(f2)
test_eq(f(0), 'int|str')
test_eq(f(''), 'int|str')
test_eq(f(0.0), 'obj')
Indexing a FastFunction works like plum.Function.invoke
but returns the most-specific matching method with the fewest parameters:
def f1(a:int, b, c): return 'int, 3 args'
def f2(a:int, b, c, d): return 'int, 4 args'
def f3(a:float, b, c): return 'float, 3 args'
def f4(a:float, b:str, c): return 'float, str, 3 args'
f = FastFunction(f1).dispatch(f1).dispatch(f2).dispatch(f3).dispatch(f4)
test_eq(f[int](0,0,0), 'int, 3 args')
test_eq(f[float](0,0,0), 'float, 3 args')
test_eq(f[float](0,0,0), 'float, 3 args')
test_eq(f[float, str](0,0,0), 'float, str, 3 args')
FastDispatcher ()
Namespace for multiple dispatched functions; extends plum.Dispatcher
Dispatching with FastDispatcher returns a FastFunction:
It supports fastcore’s backport of the |
operator on types:
@dispatch
def f(x:int|str): return 'int|str'
test_eq(f(0), 'int|str')
test_eq(f(''), 'int|str')
test_eq(f(0.0), 'obj')
… FastDispatcher.multi
works too:
@dispatch.multi([bool],[list])
def f(x:bool|list): return 'bool|list'
@dispatch
def f(x:int): return 'int'
test_eq(f(True), 'bool|list')
test_eq(f([]), 'bool|list')
test_eq(f(0), 'int')
FastDispatcher.to (cls)
Decorator: dispatch f
to cls.f
This lets you dynamically extend dispatched methods:
Now that we can dispatch on types, let’s make it easier to cast objects to a different type.
retain_meta (x, res, as_copy=False)
Call res.set_meta(x)
, if it exists
cast (x, typ)
Cast x
to typ
(may change x
inplace)
This works both for plain python classes:…
mk_class('_T1', 'a') # mk_class is a fastcore utility that constructs a class
class _T2(_T1): pass
t = _T1(a=1)
t2 = cast(t, _T2)
assert t2 is t # t2 refers to the same object as t
assert isinstance(t,_T2) # t also changed in-place
assert isinstance(t2,_T2)
test_eq_type(_T2(a=1), t2)
…as well as for arrays and tensors.
class _T1(np.ndarray): pass
t = np.array([1])
t2 = cast(t, _T1)
test_eq(np.array([1]), t2)
test_eq(_T1, type(t2))
To customize casting for other types, define a separate cast function with dispatch for your type.
retain_type (new, old=None, typ=None, as_copy=False)
Cast new
to type of old
or typ
if it’s a superclass
If old
has a _meta
attribute, its content is passed when casting new
to the type of old
. In the below example, only the attribute a
, but not other_attr
is kept, because other_attr
is not in _meta
:
def default_set_meta(self, x, as_copy=False):
"Copy over `_meta` from `x` to `res`, if it's missing"
if hasattr(x, '_meta') and not hasattr(self, '_meta'):
meta = x._meta
if as_copy: meta = copy(meta)
self._meta = meta
return self
class _A():
set_meta = default_set_meta
def __init__(self, t): self.t=t
class _B1(_A):
def __init__(self, t, a=1):
super().__init__(t)
self._meta = {'a':a}
self.other_attr = 'Hello' # will not be kept after casting.
x = _B1(1, a=2)
b = _A(1)
c = retain_type(b, old=x)
test_eq(c._meta, {'a': 2})
assert not getattr(c, 'other_attr', None)
retain_types (new, old=None, typs=None)
Cast each item of new
to type of matching item in old
if it’s a superclass
class T(tuple): pass
t1,t2 = retain_types((1,(1,(1,1))), (2,T((2,T((3,4))))))
test_eq_type(t1, 1)
test_eq_type(t2, T((1,T((1,1)))))
t1,t2 = retain_types((1,(1,(1,1))), typs = {tuple: [int, {T: [int, {T: [int,int]}]}]})
test_eq_type(t1, 1)
test_eq_type(t2, T((1,T((1,1)))))
explode_types (o)
Return the type of o
, potentially in nested dictionaries for thing that are listy