I'm curious as to how to support mixed arithmetic using a user-defined class in python. The docs state
Python fully supports mixed arithmetic: when a binary arithmetic
operator has operands of different numeric types, the operand with the
“narrower” type is widened to that of the other, where integer is
narrower than floating point, which is narrower than complex.
The behavior I am trying to recreate can be seen with numpy
import numpy as np
a = np.array([1,2,3])
a + 5
Out[3]: array([6, 7, 8])
5 + a
Out[4]: array([6, 7, 8])
If I try to do this with a user defined class, I get something like this
from decimal import Decimal
class MyType:
def __init__(self, value):
self.value = Decimal(str(value))
def __repr__(self):
return f'<MyType {self.value}>'
def __add__(self, other):
if not isinstance(other, MyType):
other = MyType(other)
return MyType(self.value + other.value)
Then attempting to do something similar gives an error when the first addition argument is a float instead of my class.
a = MyType(.1)
a + 5
Out[14]: <MyType 5.1>
5 + a
Traceback (most recent call last):
File "<ipython-input-15-35e25b55bb62>", line 1, in <module>
5 + a
TypeError: unsupported operand type(s) for +: 'int' and 'MyType'
In python you can define both __add__ and __radd__, see https://docs.python.org/3/reference/datamodel.html#object.radd
So the easiest thing in your case to do is to add __radd__ = __add__
from decimal import Decimal
class MyType:
def __init__(self, value):
self.value = Decimal(str(value))
def __repr__(self):
return f'<MyType {self.value}>'
def __add__(self, other):
if not isinstance(other, MyType):
other = MyType(other)
return MyType(self.value + other.value)
__radd__ = __add__
Seems something like link.
from decimal import Decimal
class MyType:
def __init__(self, value):
self.value = Decimal(str(value))
def __repr__(self):
return f'<MyType {self.value}>'
def __add__(self, other):
if not isinstance(other, MyType):
other = MyType(other)
return MyType(self.value + other.value)
def __radd__(self, other):
if not isinstance(other, MyType):
other = MyType(other)
return MyType(self.value + other.value)
This question already has answers here:
TypeError after overriding the __add__ method
(5 answers)
Closed 6 years ago.
consider my class mint
class mint(object):
def __init__(self, i):
self.i = i
def __add__(self, other):
o = other.i if isinstance(other, mint) else other
return mint(1 + self.i + o)
def __repr__(self):
return str(self.i)
It's designed to do another kind of addition.
a = mint(1)
a + 1 + 2
6
However, adding while my object is on the right doesn't work.
1 + a
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-519-8da568c0620a> in <module>()
----> 1 1 + a
TypeError: unsupported operand type(s) for +: 'int' and 'mint'
Question: How do I modify my class such that 1 + a will work?
You can use __radd__:
class mint(object):
def __init__(self, i):
self.i = i
def __add__(self, other):
o = other.i if isinstance(other, mint) else other
return mint(1 + self.i + o)
def __repr__(self):
return str(self.i)
def __radd__(self, other):
return self + other
a = mint(1)
print(1 + a)
Output:
3
Here's explanation from Python docs:
These methods are called to implement the binary arithmetic operations (+, -, *, #, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types. [2] For instance, to evaluate the expression x - y, where y is an instance of a class that has an rsub() method, y.rsub(x) is called if x.sub(y) returns NotImplemented.
Implement __radd__
In [1]: class mint(object):
...: def __init__(self, i):
...: self.i = i
...:
...: def __add__(self, other):
...: o = other.i if isinstance(other, mint) else other
...: return mint(1 + self.i + o)
...:
...: def __repr__(self):
...: return str(self.i)
...:
...: def __radd__(self, other):
...: return self.__add__(other)
...:
In [2]: a = mint(1)
In [3]: 1 + a
Out[3]: 3
I have a class foo that is essentially a float with some extra attributes attached. I can overwrite its __sub__ method so that I can do subtraction one direction, but I can't figure out how to do it the other way:
class foo():
def __init__(self, value, otherstuff):
self.value = value
self.otherstuff = otherstuff
def __sub__(self, other):
return self.value - other
a = 5
b = foo(12, 'blue')
print b-a # this works fine and returns 7
print a-b # I want this to return -7 but it obviously doesn't work
Is there a way to do this?
A general solution for add, sub, mul, div would be ideal, but sub and div are most pressing since they're not reversible.
You just need to override __rsub__, for right-hand side subtraction:
class foo():
def __init__(self, value, otherstuff):
self.value = value
self.otherstuff = otherstuff
def __sub__(self, other):
return self.value - other
def __rsub__(self, other):
return other - self.value
Output:
print(b - a)
7
print(a - b)
-7
There are similar methods like __radd__, __rmul__ for other operations.
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.
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))
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")
class C(object):
def __init__(self, value):
self.value = value
def __add__(self, other):
if isinstance(other, C):
return self.value + other.value
if isinstance(other, Number):
return self.value + other
raise Exception("error")
c = C(123)
print c + c
print c + 2
print 2 + c
obviously, the first two print statements will work and the third one fails because int.add() cannot deal with a class C instance.
246
125
print 2 + c
TypeError: unsupported operand type(s) for +: 'int' and 'C'
Is there a way to get around this, so 2+c will cause C.add() to be called?
You need to add __radd__ as well to handle the reverse case:
def __radd__(self, other):
if isinstance(other, C):
return other.value + self.value
if isinstance(other, Number):
return other + self.value
return NotImplemented
and note that you should not raise an exception; return the NotImplemented singleton instead. That way the other object could still try to support __add__ or __radd__ for your object and would be given a chance to implement addition too.
When you try to add two types a and b, Python first tries to call a.__add__(b); if that call returns NotImplemented, b.__radd__(a) is attempted instead.
Demo:
>>> from numbers import Number
>>> class C(object):
... def __init__(self, value):
... self.value = value
... def __add__(self, other):
... print '__add__ called'
... if isinstance(other, C):
... return self.value + other.value
... if isinstance(other, Number):
... return self.value + other
... return NotImplemented
... def __radd__(self, other):
... print '__radd__ called'
... if isinstance(other, C):
... return other.value + self.value
... if isinstance(other, Number):
... return other + self.value
... return NotImplemented
...
>>> c = C(123)
>>> c + c
__add__ called
246
>>> c + 2
__add__ called
125
>>> 2 .__add__(c)
NotImplemented
>>> 2 + c
__radd__ called
125
You need to implement __radd__ on the class.
def __radd__(self, other):
return self.value + other
This gets called automatically, since the int class will raise a NotImplemented error