You cannot use functools.singledispatch for methods in general , at least not as a decorator. Python 3.8 adds a new option, only for methods: functools.singledispatchmethod() .
It doesn't matter that Vector is not defined here yet; the first argument of any method will always be self , while you will use a single dispatch for the second argument here.
Since decorators apply to function objects before creating the class object, you can also register your βmethodsβ instead of functions outside the class body, so you have access to the Vector name:
class Vector(object): @functools.singledispatch def __mul__(self, other): return NotImplemented @Vector.__mul__.register(int) @Vector.__mul__.register(Vector) def _(self, other): result = Vector(len(self))
For unsupported types, you need to return a singleton NotImplemented , and not throw an exception. Thus, Python will also attempt to perform the inverse operation.
However, since in any case the dispatcher will enter an incorrect argument ( self ), you will have to come up with your own unified dispatch mechanism.
If you really want to use @functools.singledispatch you need to delegate a regular function with inverted arguments:
@functools.singledispatch def _vector_mul(other, self): return NotImplemented class Vector(object): def __mul__(self, other): return _vector_mul(other, self) @_vector_mul.register(int) def _vector_int_mul(other, self): result = Vector(len(self)) for j in range(len(self)): result[j] = self[j] * other return result
As for your updates using __init__mul__ : v * 3 does not translate to v.__mul__(3) . Instead, it translates to type(v).__mul__(v, 3) ; see Finding a Special Method in the Python Data Model Reference . This always bypasses any methods installed directly in the instance.
Here type(v) is Vector ; Python is looking for a function, here it will not use the associated method. Again, since functools.singledispatch sends the first argument, you cannot always use a single send directly for Vector methods, since this first argument will always be an instance of Vector .
In other words, Python will not use the methods that you set for self in __init__mul__ ; Special methods are never looked up in an instance; see Searching for special methods in the datamodel documentation.
The functools.singledispatchmethod() option, which Python 3.8 adds, uses the class as a decorator, which, like methods, implements the descriptor protocol . This allows it to then handle dispatch before binding (that is, before self is added to the argument list), and then bind the registered function, singledispatch returns the singledispatch dispatcher. The source code for this implementation is fully compatible with older versions of Python, so you can use it instead:
from functools import singledispatch, update_wrapper # Python 3.8 singledispatchmethod, backported class singledispatchmethod: """Single-dispatch generic method descriptor. Supports wrapping existing descriptors and handles non-descriptor callables as instance methods. """ def __init__(self, func): if not callable(func) and not hasattr(func, "__get__"): raise TypeError(f"{func!r} is not callable or a descriptor") self.dispatcher = singledispatch(func) self.func = func def register(self, cls, method=None): """generic_method.register(cls, func) -> func Registers a new implementation for the given *cls* on a *generic_method*. """ return self.dispatcher.register(cls, func=method) def __get__(self, obj, cls): def _method(*args, **kwargs): method = self.dispatcher.dispatch(args[0].__class__) return method.__get__(obj, cls)(*args, **kwargs) _method.__isabstractmethod__ = self.__isabstractmethod__ _method.register = self.register update_wrapper(_method, self.func) return _method @property def __isabstractmethod__(self): return getattr(self.func, '__isabstractmethod__', False)
and apply this to your Vector() class. You still need to register the Vector implementation for a separate dispatch after creating the class, because only then can you register the dispatch for the class:
class Vector(object): def __init__(self, d): self._coords = [0] * d def __setitem__(self, key, value): self._coords[key] = value def __getitem__(self, item): return self._coords[item] def __len__(self): return len(self._coords) def __repr__(self): return f"Vector({self._coords!r})" def __str__(self): return str(self._coords) @singledispatchmethod def __mul__(self, other): return NotImplemented @__mul__.register def _int_mul(self, other: int): result = Vector(len(self)) for j in range(len(self)): result[j] = self[j] * other return result @Vector.__mul__.register def _vector_mul(self, other: Vector): return sum(sc * oc for sc, oc in zip(self._coords, other._coords))
Of course, you could also first create a subclass and dispatch based on this, since dispatch also works for subclasses:
class _Vector(object): def __init__(self, d): self._coords = [0] * d class Vector(_Vector): def __setitem__(self, key, value): self._coords[key] = value def __getitem__(self, item): return self._coords[item] def __len__(self): return len(self._coords) def __repr__(self): return f"{type(self).__name__}({self._coords!r})" def __str__(self): return str(self._coords) @singledispatchmethod def __mul__(self, other): return NotImplemented @__mul__.register def _int_mul(self, other: int): result = Vector(len(self)) for j in range(len(self)): result[j] = self[j] * other return result @__mul__.register def _vector_mul(self, other: _Vector): return sum(sc * oc for sc, oc in zip(self._coords, other._coords))