Python3: singlepedispatch in class how to send your type

Using python3.4. Here I want to use singleedispatch to send another type in the __mul__ method. The code is as follows:

 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 need Vector * Vertor support, this one has a 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 the Name a Type class in a class method? I know that C ++ has a font class expression. How does python solve my problem? And it’s strange to see result = Vector(len(self)) , where Vector can be used in the body of the method. Refresh. After viewing Aa http://lukasz.langa.pl/8/single-dispatch-generic-functions/ I can choose this method for implementation:

 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() 

Anse is weird:

 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) may work, but v*3 cannot work. This is strange. From my option, v*3 is the same as v.__mul__(3) .




Update after comment by @Martijn Pieters, I still want to implement v * 3 in the class. Therefore 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__ registry:",type(v).__mul__.registry) type(v).__mul__(v, 3) print(v*3) if __name__ == "__main__": unittest.main() 

This time. I implement how I implement v.__mul__(3) . But a mistake

 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 methond should act as an instance of methond.

+9
python types class dispatch
Jun 05 '14 at 15:06
source share
2 answers

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)) # start with vector of zeros for j in range(len(self)): result[j] = self[j]*other return result 

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)) 
+12
Jun 05 '14 at 15:21
source share

This is a little ugly since you need to defer the binding of the Vector / Vector implementation of the multiplication until Vector is defined. But the idea is that for a single send function, the first argument must be of an arbitrary type, so Vector.__mul__ will call this 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") 
+1
Jun 05 '14 at 15:35
source share



All Articles