Package: dyndis
Dyndispip install dyndisAboutDyndis is a library to easily and fluently make multiple-dispatch functions and methods. It was originally made for
operators in non-strict hierarchical systems but can also serve any other multiple-dispatch purpose.Simple ExamplefromtypingimportUnionfromdyndisimportMultiDispatch@MultiDispatchdeffoo(a,b):# default implementation in case no candidates matchraiseTypeError@foo.register()def_(a:int,b:Union[int,str]):return"overload 1 <int, (int|str)>"@foo.register()def_(a:object,b:float):return"overload 2 <any, float>"foo(1,"hello")# overload 1foo(("any","object","here"),2.5)# overload 2foo(2,3)# overload 1foo(2,3.0)# overload 2Featuresdynamic upcasting.seamless usage of type-hints and type variables.advanced data structures to minimize candidate lookup time.implementor interface makes it easy to create method-like overloadsHow Does it Work?The central class (and only one users need to import) isMultiDispatch.MultiDispatchcontains candidate
implementations sorted by both priority and types of the parameters they accept. When theMultiDispatchis called, it
calls its relevant candidates (ordered by both priority, inheritance, and compatibility, expanded upon below) until one
returns a non-NotImplementedreturn value.The Lookup OrderAll candidates for parameters of types <T0, T1, T2..., TN> are ordered as follows:Any candidate with a types that is incompatible with any type in the key is excluded. That is, if for any 0 <=i<=
N, a candidate's type constraint for parameteriis not a superclass of Ti, the candidate is excluded.Candidates are ordered by inheritance. A candidate is considered to inherit another candidate if all its parameter
types are subclasses of (or are likewise covered by) the other's respective parameter type. A candidate will be
considered before any other candidate it inherits. So for example, <int,object> will be considered before <Number,
object>.If there are two candidates that do not inherit each other, an exception (of typedyndis.AmbiguityError) is raised (
unless a candidate with greater precedence than both succeeds first).If a candidate returnsNotImplemented, the next candidate in the order is tried. If no candidates are accepted or all
candidates returnedNotImplemented, the default implementation is called.Topology and Cachesdyndisuses a topological set to order all its candidates by the parameter types, so that most of the candidates can
be disregarded without any overhead.Considering all these candidates for every lookup gets quite slow and encumbering very quickly. For this reason,
everyMultiDispatchautomatically caches these computation for both sorting and processing candidates.Default, Variadic, and Keyword parametersIf a candidate has positional parameters with a default value and a type annotation, the default value will be ignored
for the purposes of candidate resolution.If a candidate has a variadic positional parameter, it is ignored. When called from aMultiDispatch, its value will
always be().If a candidate has keyword-only parameters, the parameter will not be considered for candidate types, it must either
have a default value or be set when theMultiDispatchis called.If a candidate has a variadic keyword parameter, it is ignored. When called from aMultiDispatch, its value will be
according to the (type-ignored) keyword arguments.In general, when aMultiDispatchis called with keyword arguments, those arguments are not considered for candidate
resolution and are sent to each attempted candidate as-is.ImplementorsanImplementoris a descriptor that makes it easy to create method-like candidates inside classes.fromdyndisimportMultiDispatch@MultiDispatchdefadd(self,other):returnNotImplementedclassBase:__add__=addclassA(Base):@add.implement(__qualname__)defadd(self,other:'A'):# in implementor methods, `self` is assumed to be of the owner classreturn"A+A"@add.implement(__qualname__)defadd(self,other:Base):return"A+Base"classB(Base):@add.implement(__qualname__)defadd(self,other:A):return'B+A'a=A()b=B()base=Base()a+b# A+Ba+base# A+Basea+a# A+ASpecial Type Annotationstype annotations can be of any type, or among any of these special valuestyping.Union: accepts parameters of any of the enclosed typetyping.Optional: accepts the enclosed type orNonetyping.Any: is considered a supertype for any type, includingobjectAny of typing's aliases and abstract classes such astyping.Listortyping.Sized: equivalent to their origin
type (note that specialized aliases such astyping.List[str]are invalid)typing.TypeVar: see belowNone,...,NotImplemented: equivalent to their typesTypeVarannotationsParameters can also be annotated withtyping.TypeVars. These variables bind greedily as they are encountered, and
count as matched upon first binding. After first binding, they are treated as the bound type (or the lowest constraint
of theTypeVar) for all respects.fromtypingimportTypeVar,AnyfromdyndisimportMultiDispatchT=TypeVar('T')@MultiDispatchdeffoo(*args):raiseTypeError@foo.register()def_(a:T,b:T):return"type(b) <= type(a)"@foo.register()def_(a:Any,b:Any):return"type(b) </= type(a"foo(1,1)# <=foo(1,True)# <=foo(2,'a')# </=foo(object(),object())# <=# type variables bind greedily, meaning their exact value will be equal to the first type they encounterfoo(False,2)# </=