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.
single dispatch (plural single dispatches) (computing) A dispatch method where the implementation of a function or method is chosen solely on the type of the instance calling the method.
Single-dispatch generic function decorator transforms a function into a generic function, which can have different behaviours depending upon the type of its first argument. The decorated function acts as the default implementation.
A Dispatch decorator is used to select between different implementations of the same abstract method based on the signature, or list of types. Example: # Python program to demonstrate. # dispatch decorator.
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With