Multidispatching allows you to define methods and functions which should behave
differently based on arguments’ types without cluttering
All you need is inside
generic.multidispatch module. See examples below to
learn how to use it to define multifunctions and multimethods.
First the basics:
>>> class Cat: pass >>> class Dog: pass >>> class Duck: pass
Suppose we want to define a function which behaves differently based on
arguments’ types. The naive solution is to inspect argument types with
isinstance function calls but generic provides us with
decorator which can easily reduce the amount of boilerplate and provide
desired level of extensibility:
>>> from generic.multidispatch import multidispatch >>> @multidispatch(Dog) ... def sound(o): ... print("Woof!") >>> @sound.register(Cat) ... def cat_sound(o): ... print("Meow!")
Each separate definition of
sound function works for different argument
types, we will call each such definition a multifunction case or simply a
case. We can test if our
sound multifunction works as expected:
>>> sound(Dog()) Woof! >>> sound(Cat()) Meow! >>> sound(Duck()) Traceback (most recent call last): ... TypeError: No available rule found for ...
The main advantage of using multifunctions over single function with a bunch of
isinstance checks is extensibility – you can add more cases for other types
even in separate module:
>>> @sound.register(Duck) ... def duck_sound(o): ... print("Quack!")
When behaviour of multifunction depends on some argument we will say that this multifunction dispatches on this argument.
You can also define multifunctions of several arguments and even decide on which of first arguments you want to dispatch. For example the following function will only dispatch on its first argument while requiring both of them:
>>> @multidispatch(Dog) ... def walk(dog, meters): ... print("Dog walks for %d meters" % meters)
But sometimes you want multifunctions to dispatch on more than one argument,
then you just have to provide several arguments to
and to subsequent
>>> @multidispatch(Dog, Cat) ... def chases(dog, cat): ... return True >>> @chases.register(Dog, Dog) ... def chases_dog_dog(dog1, dog2): ... return None >>> @chases.register(Cat, Dog) ... def chases_cat_dog(cat, dog): ... return False
You can have any number of arguments to dispatch on but they should be all positional, keyword arguments are allowed for multifunctions only if they’re not used for dispatch.
Another functionality provided by
generic.multimethod module are
multimethods. Multimethods are similar to multifunctions except they are…
methods. Technically the main and the only difference between multifunctions and
multimethods is the latter is also dispatch on
Implementing multimethods is similar to implementing multifunctions, you just
have to decorate your methods with
multimethod decorator instead of
multidispatch. But there’s some issue with how Python’s classes works which
forces us to use also
has_multimethods class decorator:
>>> class Vegetable: pass >>> class Meat: pass >>> from generic.multimethod import multimethod, has_multimethods >>> @has_multimethods ... class Animal(object): ... ... @multimethod(Vegetable) ... def can_eat(self, food): ... return True ... ... @can_eat.register(Meat) ... def can_eat(self, food): ... return False register rule (<class '__main__.Animal'>, <class '__main__.Vegetable'>) register rule (<class '__main__.Animal'>, <class '__main__.Meat'>)
This would work like this:
>>> animal = Animal() >>> animal.can_eat(Vegetable()) True >>> animal.can_eat(Meat()) False
So far we haven’t seen any differences between multifunctions and multimethods
but as it have already been said there’s one – multimethods use
argument for dispatch. We can see that if we would subclass our
can_eat method definition:
>>> @has_multimethods ... class Predator(Animal): ... @Animal.can_eat.register(Meat) ... def can_eat(self, food): ... return True register rule (<class '__main__.Predator'>, <class '__main__.Meat'>)
This will override
Predator instances but only for the case
Meat argument, case for the
Vegetable is not overridden, so class
inherits it from
>>> predator = Predator() >>> predator.can_eat(Vegetable()) True >>> predator.can_eat(Meat()) True
The only thing to care is you should not forget to include
decorator on classes which define or override multimethods.
You can also provide a “catch-all” case for multimethod using
decorator just like in example for multifunctions.
There should be an analog to
else statement – a case which is used when no
matching case is found, we will call such case a catch-all case, here is how
you can define it using
>>> @has_multimethods ... class Animal(object): ... ... @multimethod(Vegetable) ... def can_eat(self, food): ... return True ... ... @can_eat.register(Meat) ... def can_eat(self, food): ... return False ... ... @can_eat.otherwise ... def can_eat(self, food): ... return "?" register rule (<class '__main__.Animal'>, <class '__main__.Vegetable'>) register rule (<class '__main__.Animal'>, <class '__main__.Meat'>) register rule (<class '__main__.Animal'>, <class 'object'>) >>> Animal().can_eat(1) '?'
You can try calling
sound with whatever argument type you wish, it will
never fall with
- generic.multidispatch.multidispatch(*argtypes: Optional[type]) Callable[[generic.multidispatch.T], generic.multidispatch.FunctionDispatcher[generic.multidispatch.T]] ¶
Declare function as multidispatch.
This decorator takes
argtypesargument types and replace decorated function with
FunctionDispatcherobject, which is responsible for multiple dispatch feature.
- generic.multimethod.multimethod(*argtypes: Optional[type]) Callable[[generic.multimethod.T], generic.multimethod.MethodDispatcher[generic.multimethod.T]] ¶
Declare method as multimethod.
Should be used only for decorating methods and enclosing class should have
- generic.multimethod.has_multimethods(cls: type[~ C]) type[~C] ¶
Declare class as one that have multimethods.
Should only be used for decorating classes which have methods decorated with
- class generic.multidispatch.FunctionDispatcher(argspec: inspect.FullArgSpec, params_arity: int)¶
Multidispatcher for functions.
This object dispatch calls to function by its argument types. Usually it is produced by
You should not manually create objects of this type.
- register(*argtypes: Optional[type]) Callable[[generic.multidispatch.T], generic.multidispatch.T] ¶
Decorator for registering new case for multidispatch.
New case will be registered for types identified by
argtypes. The length of
argtypesshould be equal to the length of
argtypesargument were passed corresponding
multidispatch()call, which also indicated the number of arguments multidispatch dispatches on.
- class generic.multimethod.MethodDispatcher(argspec: inspect.FullArgSpec, params_arity: int)¶
Multiple dispatch for methods.
This object dispatch call to method by its class and arguments types. Usually it is produced by
You should not manually create objects of this type.
- property otherwise: Callable[[generic.multimethod.T], generic.multimethod.T]¶
Decorator which registers “catch-all” case for multimethod.