patternpythonMinorCanonical
Multiple dispatch decorator classes in Python
Viewed 0 times
dispatchpythonclassesmultipledecorator
Problem
This is based on my first review and the suggested points in "Multiple dispatch decorator in Python":
```
import inspect
from functools import wraps, update_wrapper, partial
class multi_dispatch(object):
"""
Returns a multiple dispatch version of the function, that has a "register"
method with which new versions of the function can be registered. These
functions each must have annotated parameters with the types they shall be
called with. Keyword parameters and "self" must not be annotated.
The decorated function works by searching for an exact match for the types
of the given arguments. If no match is found, the function on which this
decorator has been used, is called. If you don't want this, raise a
TypeError inside the function.
>>> FMT = 'In {} the {} and {} collide.'
>>> FMT_SA = 'In {} the {} is smashed to bits by {}.'
>>> FMT_AS = 'In {} the {} is hit by the {}.'
>>>
>>> class GameObject(object):
... def __init__(self, name):
... self.name = name
...
... def __repr__(self):
... return self.name
...
...
>>> class Asteroid(GameObject):
... pass
...
...
>>> class Spaceship(GameObject):
... pass
...
...
>>> class Starcluster(GameObject):
... @multi_dispatch
... def collide(self, a, b):
... print(FMT.format(self, a, b))
...
... @collide.register
... def collide(self, a: Spaceship, b: Asteroid):
... print(FMT_SA.format(self, a, b))
...
... @collide.register
... def collide(self, a: Asteroid, b: Spaceship):
... print(FMT_AS.format(self, a, b))
...
...
>>> a = Asteroid('Iris')
>>> s = Spaceship('Voyager')
>>> sc = Starcluster('Messier 69')
>>>
>>> sc.collide(a, s)
In Messier 69 the Iris is hit by the Voyager.
>>> sc.collide(s, 'a grain of dust')
In Messier 69 the Voyager and a grain
```
import inspect
from functools import wraps, update_wrapper, partial
class multi_dispatch(object):
"""
Returns a multiple dispatch version of the function, that has a "register"
method with which new versions of the function can be registered. These
functions each must have annotated parameters with the types they shall be
called with. Keyword parameters and "self" must not be annotated.
The decorated function works by searching for an exact match for the types
of the given arguments. If no match is found, the function on which this
decorator has been used, is called. If you don't want this, raise a
TypeError inside the function.
>>> FMT = 'In {} the {} and {} collide.'
>>> FMT_SA = 'In {} the {} is smashed to bits by {}.'
>>> FMT_AS = 'In {} the {} is hit by the {}.'
>>>
>>> class GameObject(object):
... def __init__(self, name):
... self.name = name
...
... def __repr__(self):
... return self.name
...
...
>>> class Asteroid(GameObject):
... pass
...
...
>>> class Spaceship(GameObject):
... pass
...
...
>>> class Starcluster(GameObject):
... @multi_dispatch
... def collide(self, a, b):
... print(FMT.format(self, a, b))
...
... @collide.register
... def collide(self, a: Spaceship, b: Asteroid):
... print(FMT_SA.format(self, a, b))
...
... @collide.register
... def collide(self, a: Asteroid, b: Spaceship):
... print(FMT_AS.format(self, a, b))
...
...
>>> a = Asteroid('Iris')
>>> s = Spaceship('Voyager')
>>> sc = Starcluster('Messier 69')
>>>
>>> sc.collide(a, s)
In Messier 69 the Iris is hit by the Voyager.
>>> sc.collide(s, 'a grain of dust')
In Messier 69 the Voyager and a grain
Solution
The code itself looks quite pretty and overall readable, considering that the topic itself requires some knowledge about the Python language internals. Some suggestions in descending order of urgency:
-
You should either document that
-
It might be sensible to add a sanity-check in case someone annotates a keyword argument.
-
I prefer to use formats instead of string concatenation, even for exception messages:
This allows for easier extension of the error message (in case it turns out to be too shallow at some point; one might want to include the overloaded functions name at some point in the future) and makes it easier to read.
Explicitly inheriting from
-
You should either document that
@classmethod and @staticmethod are not supported, or implement support for them.-
It might be sensible to add a sanity-check in case someone annotates a keyword argument.
-
I prefer to use formats instead of string concatenation, even for exception messages:
raise TypeError("duplicate registration of: {}".format(
annotation))This allows for easier extension of the error message (in case it turns out to be too shallow at some point; one might want to include the overloaded functions name at some point in the future) and makes it easier to read.
Explicitly inheriting from
object is not required anymore in Python 3, but it is not wrong to explicitly specify that.Code Snippets
raise TypeError("duplicate registration of: {}".format(
annotation))Context
StackExchange Code Review Q#57019, answer score: 3
Revisions (0)
No revisions yet.