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 of dust collide. >>> >>> >>> @multi_dispatch ... def collide(a, b): ... print(FMT.format('outer space', a, b)) ... ... >>> @collide.register ... def collide(a: Spaceship, b: Asteroid): ... print(FMT_SA.format('outer space', a, b)) ... ... >>> @collide.register ... def collide(a: Asteroid, b: Spaceship): ... print(FMT_AS.format('outer space', a, b)) ... ... >>> collide(a, s) In outer space the Iris is hit by the Voyager. >>> collide(s, a) In outer space the Voyager is smashed to bits by Iris. >>> collide(s, 'a grain of dust') In outer space the Voyager and a grain of dust collide. """ def __init__(self, function): update_wrapper(self, function) self.default_function = function self._annotation_to_function = {} self._is_bound_method = False def __get__(self, obj, objtype): self._is_bound_method = True return partial(self.__call__, obj) def register(self, function): annotation_to_function = self._annotation_to_function annotation = self._get_annotations(function) if annotation in annotation_to_function: raise TypeError("duplicate registration of: " + str(annotation)) annotation_to_function[annotation] = function return self def _get_annotations(self, function, empty=inspect.Parameter.empty): parameters = inspect.signature(function).parameters annotations = (a.annotation for a in parameters.values()) return tuple(i for i in annotations if i is not empty) def __call__(self, *args, **kwargs): annotation_to_function = self._annotation_to_function types = tuple(type(a) for a in args) if self._is_bound_method: types = types[1:] if types not in annotation_to_function: return self.default_function(*args, **kwargs) return annotation_to_function[types](*args, **kwargs) class inheritance_multi_dispatch(multi_dispatch): """ >>> 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): ... @inheritance_multi_dispatch ... def collide(self, a, b): ... print(FMT.format(self, a, b)) ... ... @collide.register ... def collide(self, a: (Spaceship, Asteroid), b: str): ... 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, 'a teapot') In Messier 69 the Iris is smashed to bits by a teapot. >>> sc.collide(s, 'a teapot') In Messier 69 the Voyager is smashed to bits by a teapot. >>> sc.collide(a, s) In Messier 69 the Iris is hit by the Voyager. >>> sc.collide('Moon', 'a stone') In Messier 69 the Moon and a stone collide. """ def __init__(self, function): self._cache = {} self.set_argument_to_annotation_matcher(isinstance) super().__init__(function) def set_argument_to_annotation_matcher(self, matcher): self._matcher = matcher def register(self, function): self._cache = {} return super().register(function) def __call__(self, *args, **kwargs): cache = self._cache types = tuple(type(a) for a in args) if self._is_bound_method: types = types[1:] try: method = cache[types] except KeyError: cache[types] = method = self._find_match(args) return method(*args, **kwargs) def _find_match(self, args): annotation_to_function = self._annotation_to_function matcher = self._matcher if self._is_bound_method: args = args[1:] for annotations, function in annotation_to_function.items(): if all(matcher(arg, ann) for arg, ann in zip(args, annotations)): return function return self.default_function
Again, I'd like to read what you think about the code from every point of view!
autopep8.py
enabled, so it should conform mostly, but it's sadly not perfect yet.)\$\endgroup\$CapitalizedWords
.\$\endgroup\$lowercase
makes more sense in this case, because they're used as a function. (So this an exception, as stated in this answer.)\$\endgroup\$