Editor

Multiple Dispatch

#458 · Functions · Expert · 230 XP · about 50 min

functools.singledispatch chooses an implementation by the type of the first argument. Build multidispatch, which looks at the types of all the positional arguments.

- multidispatch(func) returns a dispatcher: an object you call like a function, which has a method register(*types). - register(*types) returns a decorator. The decorator records the function it decorates as the implementation for those types (one class per positional argument) and returns that function unchanged. Registering the same types again replaces the old implementation. When the dispatcher is called with positional arguments args:

  • A registration matches when it has exactly one type per argument and each argument is an instance of its type.
  • For a matching registration, the distance of each argument is the position of its registered type in type(arg).__mro__: 0 for the argument's own class, 1 for its parent, and so on, following the method resolution order.
  • The winner is the matching registration whose distances are all less than or equal to the corresponding distances of every other matching registration. The dispatcher calls it with args and returns its result.
  • If no registration matches, the dispatcher calls the original func with args and returns its result.
  • If registrations match but none of them is the winner, it raises TypeError.

Examples

class Shape: pass
class Circle(Shape): pass
class Square(Shape): pass

@multidispatch
def overlap(a, b):
    return 'unknown'

@overlap.register(Shape, Shape)
def _(a, b):
    return 'bounding boxes'

@overlap.register(Circle, Circle)
def _(a, b):
    return 'compare radii'

@overlap.register(Circle, Shape)
def _(a, b):
    return 'circle first'

@overlap.register(Shape, Circle)
def _(a, b):
    return 'circle second'

overlap(Circle(), Circle())   # returns 'compare radii'    distances (0, 0)
overlap(Circle(), Square())   # returns 'circle first'     (0, 1) beats (1, 1)
overlap(Square(), Square())   # returns 'bounding boxes'
overlap(Square(), 5)          # returns 'unknown'          nothing matches

Without the (Circle, Circle) registration, overlap(Circle(), Circle()) would raise TypeError: (Circle, Shape) has distances (0, 1), (Shape, Circle) has (1, 0), and neither is as close as the other in both places.

Constraints

  • Calls pass positional arguments only, and the classes in the tests are ordinary classes (no abstract base classes)
  • Up to 300 registrations and 50,000 calls per dispatcher, with only a few different combinations of argument types, so working out the winner from scratch on every call is too slow

Fastest solvers

Leaderboard

First solves only, ranked by stars and then time.