19

Using python3.4. Here I want use singledispatch to dispatch different type in __mul__ method . The code like this :

class Vector(object):

    ## some code not paste  
    @functools.singledispatch
    def __mul__(self, other):
        raise NotImplementedError("can't mul these type")

    @__mul__.register(int)
    @__mul__.register(object)                # Becasue can't use Vector , I have to use object 
    def _(self, other):
        result = Vector(len(self))           # start with vector of zeros
        for j in range(len(self)):
            result[j] = self[j]*other
        return result

    @__mul__.register(Vector)                # how can I use the self't type
    @__mul__.register(object)                # 
    def _(self, other):
        pass # need impl 

As you can see the code , I want support Vector*Vertor , This has Name error

Traceback (most recent call last):
  File "p_algorithms\vector.py", line 6, in <module>
    class Vector(object):
  File "p_algorithms\vector.py", line 84, in Vector
    @__mul__.register(Vector)                   # how can I use the self't type
NameError: name 'Vector' is not defined

The question may be How can I use class name a Type in the class's method ? I know c++ have font class statement . How python solve my problem ? And it is strange to see result = Vector(len(self)) where the Vector can be used in method body .


After have A look at http://lukasz.langa.pl/8/single-dispatch-generic-functions/ I can choose this way to implement :

import unittest
from functools import  singledispatch

class Vector(object):
    """Represent a vector in a multidimensional space."""

    def __init__(self, d):
        self._coords = [0 for i in range(0, d)]
        self.__init__mul__()


    def __init__mul__(self):
        __mul__registry = self.__mul__.registry
        self.__mul__ = singledispatch(__mul__registry[object])
        self.__mul__.register(int, self.mul_int)
        self.__mul__.register(Vector, self.mul_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 __str__(self):
        return str(self._coords)

    @singledispatch
    def __mul__(self, other):
        print ("error type is ", type(other))
        print (type(other))
        raise NotImplementedError("can't mul these type")

    def mul_int(self,other):
         print ("other type is ", type(other))
         result = Vector(len(self))           # start with vector of zeros
         for j in range(len(self)):
             result[j] = self[j]*other
         return result

    def mul_Vector(self, other):
        print ("other type is ", type(other))
        #result = Vector(len(self))           # start with vector of zeros
        sum = 0
        for i in range(0,len(self)):
            sum += self._coords[i] * other._coords[i]
        return sum

class TestCase(unittest.TestCase):
    def test_singledispatch(self):
        # the following demonstrates usage of a few methods
        v = Vector(5)              # construct five-dimensional <0, 0, 0, 0, 0>
        for i in range(1,6):
            v[i-1] = i
        print(v.__mul__(3))
        print(v.__mul__(v))
        print(v*3)

if __name__ == "__main__":
    unittest.main()

The answer is strange :

other type is  <class 'int'>
[3, 6, 9, 12, 15]
other type is  <class '__main__.Vector'>
55
error type is  <class 'int'>
Traceback (most recent call last):
  File "p_algorithms\vector.py", line 164, in <module>
    print(v*3)
  File "C:\Python34\lib\functools.py", line 710, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File "p_algorithms\vector.py", line 111, in __mul__
    raise NotImplementedError("can't mul these type")

v.__mul__(3) can work but v*3 can't work. This is strange From my option v*3 is just the same as v.__mul__(3) .


Update after @Martijn Pieters's comment, I still want implement v*3 in class. So I try this

import unittest
from functools import  singledispatch

class Vector(object):

    @staticmethod
    def static_mul_int(self,other):
         print ("other type is ", type(other))
         result = Vector(len(self))           # start with vector of zeros
         for j in range(len(self)):
             result[j] = self[j]*other
         return result

    @singledispatch
    @staticmethod
    def __static_mul__(cls, other):
        print ("error type is ", type(other))
        print (type(other))
        raise NotImplementedError("can't mul these type")


    __mul__registry2 = __static_mul__.registry
    __mul__ = singledispatch(__mul__registry2[object])
    __mul__.register(int, static_mul_int)

    def __init__(self, d):
        self._coords = [0 for i in range(0, d)]
        self.__init__mul__()


    def __init__mul__(self):
        __mul__registry = self.__mul__.registry
        print ("__mul__registry",__mul__registry,__mul__registry[object])
        self.__mul__ = singledispatch(__mul__registry[object])
        self.__mul__.register(int, self.mul_int)
        print ("at last __mul__registry",self.__mul__.registry)

    # @singledispatch
    # def __mul__(self, other):
    #     print ("error type is ", type(other))
    #     print (type(other))
    #     raise NotImplementedError("can't mul these type")


    def mul_int(self,other):
         print ("other type is ", type(other))
         result = Vector(len(self))           # start with vector of zeros
         for j in range(len(self)):
             result[j] = self[j]*other
         return result

    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 __str__(self):
        return str(self._coords)


class TestCase(unittest.TestCase):
    def test_singledispatch(self):
        # the following demonstrates usage of a few methods
        v = Vector(5)              # construct five-dimensional <0, 0, 0, 0, 0>
        for i in range(1,6):
            v[i-1] = i
        print(v.__mul__(3))
        print("type(v).__mul__'s registry:",type(v).__mul__.registry)
        type(v).__mul__(v, 3)
        print(v*3)

if __name__ == "__main__":
    unittest.main() 

This time . v.__mul__(3) have error :

Traceback (most recent call last):
  File "test.py", line 73, in test_singledispatch
    type(v).__mul__(v, 3)
  File "/usr/lib/python3.4/functools.py", line 708, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
TypeError: 'staticmethod' object is not callable

For me static method should act like the instance method.

jiamo
  • 1,406
  • 1
  • 17
  • 29
  • `v * 3` is not the same as `v.__mul__(3)`; it is the same as `type(v).__mul__(v, 3)`. See [*Special method lookup*](https://docs.python.org/3/reference/datamodel.html#special-method-lookup). – Martijn Pieters Jul 23 '14 at 07:53
  • What i code v.__mul__(3), is indeed like __mul__(v,3), Here v is the object , 3 is the type I register in ·__init__mul__· (like I bind the second arg of this fun to dispatch). if type(v).__mul__(v, 3) is not same . what's the difference with v.__mul__(3). In c language level ? – jiamo Jul 23 '14 at 09:48
  • It is important to know that Python functions act as descriptors; see [From Function to Method](https://wiki.python.org/moin/FromFunctionToMethod) and [descriptor HOWTO](https://docs.python.org/3/howto/descriptor.html); this is how Python *binds* a function to an instance, giving you a method. – Martijn Pieters Jul 23 '14 at 09:55
  • However, for *special methods* (so `__mul__` and `__add__` and `__hash__`, etc., to support creating a hash of a class and other usecases), Python *bypasses* bound methods and instead manually passes in `self`; `type(v).__mul__(v, 3)` takes the unbound function and passes in `v` as `self` directly. – Martijn Pieters Jul 23 '14 at 10:04
  • It isn't clear why you are asking your mini question in your bounty message; either way, you cannot use `functools.singledispatch` on methods, because decorators always apply to the *unbound function*, not to the method, so the first argument is *always* `self`. My answer sidesteps the issue by delegating the method to a separate, outside singledispatch function instead. – Martijn Pieters Jul 23 '14 at 10:07
  • so v.__mul__(v). here the true func only take one arg? That's why my `__init__mul__` can work ? Here I seem can't understand why it can make v.__mul__(v) dispatch right? But how the 'self' arg passed in bounded function? – jiamo Jul 23 '14 at 11:56
  • Your `__init__mul__` cannot work because special methods are never looked up on the instance. Python will not use those because it uses `type(v).__mul__` instead of `v.__mul__`, always. That's why I pointed you to the [*Special method lookup* documentation](https://docs.python.org/3/reference/datamodel.html#special-method-lookup). – Martijn Pieters Jul 23 '14 at 12:00
  • So there are two issues here: `__init__mul__` won't work because you are working with special methods and those won't be taken from the instance as Python goes straight to the class and provides the `self` argument manually. And because `__mul__` is then *always* passed in a `Vector` first argument, you **cannot** use single dispatch on class methods. – Martijn Pieters Jul 23 '14 at 12:03

2 Answers2

30

You cannot use functools.singledispatch on methods at all, not as a decorator at least. Python 3.8 adds a new option, just for methods: functools.singledispatchmethod().

It doesn't matter that Vector isn't defined here yet; the first argument to any method is always going to be self, while you'd use single dispatch for the second argument here.

Because decorators apply to the function objects before the class object is created, you could just as well register your 'methods' as functions instead, outside of 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))           # start with vector of zeros
    for j in range(len(self)):
        result[j] = self[j]*other
    return result

For non-supported types, you need to return the NotImplemented singleton, not raise an exception. This way Python will try the inverse operation too.

However, since the dispatch is going to key on the wrong argument (self) here anyway, you'll have to come up with your own single dispatch mechanism.

If you really want to use @functools.singledispatch you'd have to delegate to a regular function, with the arguments inversed:

@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 is not translated to v.__mul__(3). It is instead translated to type(v).__mul__(v, 3), see Special method lookup in the Python datamodel reference. This always bypasses any methods set directly on the instance.

Here type(v) is Vector; Python looks up the function, it won't use a bound method here. Again, because functools.singledispatch dispatches on the first argument, always, you cannot use single dispatch directly on the methods of Vector, because that first argument is always going to be a Vector instance.

In other words, Python will not use the methods you set on self in __init__mul__; special methods are never looked up on the instance, see Special method lookup in the datamodel documentation.

The functools.singledispatchmethod() option that Python 3.8 adds uses a class as the decorator which implements the descriptor protocol, just like methods do. This lets it then handle dispatch before binding (so before self would be prepended to the argument list) and then bind the registered function that the singledispatch dispatcher returns. The source code for this implementation is fully compatible with older Python versions, so you could use that 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 that to your Vector() class. You still have to register your Vector implementation for the single dispatch after the class has been created, because only then can you register a 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))

You could of course also create a subclass first and dispatch based on that, since dispatch works for subclasses too:

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))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Could you define a static method `_mul_by_others(other, self)` for use with single dispatch, then have `def __mul__(self, other): return Vector._mul_by_other(other, self)`? – chepner Jun 05 '14 at 15:24
  • @chepner: I guess so; then at least the first argument is the other type. – Martijn Pieters Jun 05 '14 at 15:26
  • 1
    @Sardathrion: ah, and I remember the **other** reason this doesn't work: special methods are looked up on the **type**, not the instance. So setting a `__mul__` attribute on the instance is going to be ignored and so is the singledispatch hook. – Martijn Pieters Sep 22 '17 at 12:44
  • things have changed in 3.8 https://stackoverflow.com/q/24601722/5986907 – joel Jun 12 '19 at 14:06
  • @JoelBerkeley: thanks for the reminder, I've updated this answer to pull in that implementation. – Martijn Pieters Jun 12 '19 at 15:19
4

This is a little ugly, as you need to defer binding the implementation of Vector/Vector multiplication until after Vector is actually defined. But the idea is that the single-dispatch function needs the first argument to be of arbitrary type, so Vector.__mul__ will call that function with self as the second argument.

import functools

class Vector:

    def __mul__(self, other):
        # Python has already dispatched Vector() * object() here, so
        # swap the arguments so that our single-dispatch works. Note
        # that in general if a*b != b*a, then the _mul_by_other
        # implementations need to compensate.
        return Vector._mul_by_other(other, self)

    @functools.singledispatch
    def _mul_by_other(x, y):
        raise NotImplementedError("Can't multiply vector by {}".format(type(x)))

    @_mul_by_other.register(int)
    def _(x, y):
        print("Multiply vector by int")

@Vector._mul_by_other.register(Vector)
def _(x, y):
    print("Multiply vector by another vector")

x = Vector()
y = Vector()
x * 3
x * y
try:
    x * "foo"
except NotImplementedError:
    print("Caught attempt to multiply by string")
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I can use Vector in fun body, like Vector() . Indeed , the decorator to the methond in class is liking static var make self.__mul__ = @functools.singledispatch(self.__mul__) so this eval before a class was define ? – jiamo Jun 05 '14 at 16:12
  • still have a fun outside the Vector , Can we have a way move it in class . – jiamo Jun 05 '14 at 16:15
  • 1
    You can only use `Vector()` inside a method definition, because the name isn't actually looked up until you call the function *after* the (global) name `Vector` has been bound to the class you are defining. You can't use `Vector` at class scope, because the name doesn't exit yet, and Python doesn't have forward declarations. – chepner Jun 05 '14 at 16:48