Works with inheritance and all kinds of methods (instance methods, classmethods and staticmethods).
Lots of metaclass magic. Use with caution.
This repo is an implementation of multiple-dispatch in python using metaclasses.
Probably not! Only the most basic tests have been done on it.
If you're looking for multiple-dispatch solutions, you should probably look at plum-dispatch
or multipledispatch
, both of which are much more mature projects than this one.
This repo is more for demonstration purposes than anything else. However, it does have some features that neither of the above projects have.
multipledispatch
is a really great implementation of what we're trying to do here.
It's really nice for the simplicity of the syntax, and the fact that there are so few dependencies.
However, it doesn't work particularly well with an object-oriented approach to programming, one of the most popular programming paradigms in Python.
plum-dispatch
is much more featureful than multipledispatch.
It's still light on performance overhead, but works much better with class inheritance: a subclass can inherit a "fallback" implementation from a base class, which is a really valuable feature.
Unlike multipledispatch
, numpy
is listed as a requirement, but there are no other large dependencies in the package.
It also does other useful things that this implementation doesn't (yet) do:
- Forward references are permitted when describing the argument-types that a function implementation should accept.
- It raises a useful
AmbiguityLookupError
in situations where it's not clear which implementation would be the best fit.
However, both of these great projects have one important failing (in my view): neither of them support classmethods or staticmethods! This makes me sad ☹️
This new implementation does (although you won't be able to use the usual decorators; instead, you'll need to pass special keyword arguments to the @inherited_dispatch
decorator provided here).
N.B. This implementation is specifically for multiple-dispatch methods, not multiple-dispatch functions outside of classes.
Lots of evil magic involving metaclasses, inheritance and dunder methods.
I haven't done rigorous performance benchmarks, but a basic test indicates that instantiation of class instances takes around 10x the time as a class using functools.singledispatchmethod
.
It is likely that other aspects of performance will also be slower, due to the use of metaclasses.
This is not an apples-to-apples comparison: this module provides a large number of features not available with functools.singledispathmethod
.
However, you should think about whether performance might be an issue before using this module.
Rules for use are at the bottom of this README. Examples for how to use it can be seen in examples.py.
This is not on pip, as it is a single file with 0 external dependencies, so just copy it into your code base if you are crazy enough to use it. Python 3.9+ is required. Please credit me in some form, ideally linking to this repo.
No!!
-
All classes wishing to access "inherited multiple dispatch features" must use
InheritedDispatchMeta
as the metaclass. -
InheritedDispatchBase
is provided as a convenient base class that can be used (see examples.py), but it is not essential to inherit fromInheritedDispatchBase
. You could very easily write your own base class, if it usesInheritedDispatchMeta
as its metaclass. -
You must use the
@inherited_dispatch decorator
on all functions you want to be able to access "inherited multiple dispatch" features. -
Methods decorated with
@inherited_dispatch
must have all arguments type-annotated. (This includes type-annotatingself
,cls
, etc. -- it will be cleaner if you dofrom __future__ import annotations
.) -
There must be exactly one registered implementation of a multiple-dispatch function that has the argument
base_impl=True
passed to the@inherited_dispatch
decorator. This informs the module that this implementation should be seen as the "fallback" implementation. -
The types of the arguments passed to a function must exactly match the types given in an implementation annotation in order for that implementation to be returned. Anything else, and the fallback implementation is returned.
There are two exceptions to this: if you annotate an argument with
typing.TypeVar
and "bind" theTypeVar
to a class, any subclasses of the bound class will "match" the annotation.TypeVar
s may be bound totyping.Union
as well as to a single class. However, they may not be bound to forward references.The other exception is
typing.Any
, which will match with any argument type. -
Be aware that if you use flexible/ambiguous types such as
typing.TypeVar
,typing.Any
ortyping.Union
, this module makes no guarantee that it will find the "best" implementation that "most closely" matches the types of the arguments provided. It will return the first implementation it finds that matches all of the argument types -- it's on you if you're not specific enough! -
Allowed "special types" for annotations are:
-
typing.Annotated
(Resolves to a normal type whentyping.get_type_hints()
is called on it) typing.Any
typing.Union
- Paramaterised generics (
list[str]
etc.) - Parameterised
typing.Final
-
typing.TypeVar
, if it's been bound to a type. -
typing.Protocol
if it's marked as being runtime_checkable - Paramaterised
collections.abc
(MutableSequence[str]
etc) typing.Callable
-
-
Using the
|
operator between types in annotations is disallowed (usetyping.Union
instead). Other features introduced in python 3.10 may not work either; this has only been tested on python 3.9. -
Disallowed types for annotations are the following. (Implementation for some of these would be possible but would probably slow it down/complicate it more.)
typing.Literal
-
typing.Protocol
if it's not marked as being runtime_checkable typing.Generic
typing.ClassVar
- unparamaterised
typing.Final
-
classmethods and staticmethods are possible, but you cannot use the standard builtin
@classmethod
and@staticmethod
decorators. Instead, mark the methods as classmethods or staticmethods by passingclass_method=True
orstatic_method=True
into the@inherited_dispatch
decorator. -
All implementations of any one method must be the same 'kind' of method -- they must all either be instance methods, all static methods, or all class methods.
-
You can't use the
@inherited_dispatch
decorator on dunder methods (that would be crazy!) -
Only a base implementation of a function is allowed to have a variable number of arguments. All other implementations of a function must have a fixed number of positional and keyword arguments.
-
The base implementation may have a variable number of positional arguments, or a variable number of keyword arguments. However, it may not have both.