Reputation: 2469
Consider the following example of a 'wrapper' class to represent vectors:
class Vector:
def __init__(self, value):
self._vals = value.copy()
def __add__(self, other):
if isinstance(other, list):
result = [x+y for (x, y) in zip(self._vals, other)]
elif isinstance(other, Vector):
result = [x+y for (x, y) in zip(self._vals, other._vals)]
else:
# assume other is scalar
result = [x+other for x in self._vals]
return Vector(result)
def __str__(self):
return str(self._vals)
The __add__
method takes care of adding two vectors as well as adding a vector with a scalar. However, the second case is not complete as the following examples show:
>>> a = Vector([1.2, 3, 4])
>>> print(a)
[1.2, 3, 4]
>>> print(a+a)
[2.4, 6, 8]
>>> print(a+5)
[6.2, 8, 9]
>>> print(5+a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'Vector'
To my understanding the reason is that the overloaded operator only tells Python what to do when it sees a + x
where a
is an instance of Vector
, but there is no indication of what to do for x + a
(with a
an instance of Vector
and x
a scalar).
How one should overload the operators in such circumstances to cover all cases (i.e., to support the case that self
is not an instance of Vector
but other
is)?
Upvotes: 3
Views: 709
Reputation: 2551
You already figured out you need to implement __radd__
. This is an answer as to why this is so, and why you need to do this in addition to implementing __add__
, as a Both quotes are taken from Python Docs (Data Model - 3.3.8 Emulating numeric types), starting with the obvious:
These methods are called to implement the binary arithmetic operations (
+
,-
,*
,@
,/
,//
,%
,divmod()
,pow()
,**
,<<
,>>
,&
,^
,|
). For instance, to evaluate the expressionx + y
, where x is an instance of a class that has an__add__()
method,x.__add__(y)
is called.
So order determines which object's implementation of __add__
is called. When the method doesn't support the operation with the passed argument NotImplemented
should be returned. That's where the so-called "reflected methods" come into play:
These functions are only called if the left operand does not support the corresponding operation and the operands are of different types. For instance, to evaluate the expression
x - y
, wherey
is an instance of a class that has an__rsub__()
method,y.__rsub__(x)
is called ifx.__sub__(y)
returnsNotImplemented
[sic].
Now, why wouldn't __radd__(self, other)
just fall back to __add__(self, other)
? While ring addition is always commutative (see this and this math.stackexchange answers), you could have algebraic structures that are do not satisfy this assumption (e.g., near-rings). But my guess as a non-mathematician would be that it's just desirable to have a consistent data model across different numerical methods. While addition might be commonly commutative, multiplication is less so. (Think matrices and vectors! Although, admittedly this is not the best example, given __matmul__
). I also prefer to see there being no exceptions, especially if I had to read about rings, etc.
in a language documentation.
Upvotes: 0
Reputation: 2469
Ok. I guess I found the answer: one has to overload __radd__
operator as well:
class Vector:
def __init__(self, value):
self._vals = value.copy()
def __add__(self, other):
if isinstance(other, list):
result = [x+y for (x, y) in zip(self._vals, other)]
elif isinstance(other, Vector):
result = [x+y for (x, y) in zip(self._vals, other._vals)]
else:
# assume other is scalar
result = [x+other for x in self._vals]
return Vector(result)
def __radd__(self, other):
return self + other
def __str__(self):
return str(self._vals)
Although to me this looks a bit redundant. (Why Python does not use the commutativity of addition by default, assuming __radd__(self, other)
always returns self + other
? Of course for special cases the user can override __radd__
.)
Upvotes: 1
Reputation: 1712
You could define a Scalar
class that has int
as its base class.
Then override __add__
to do what you want.
class Scalar(int):
def __add__(self):
# do stuff
Upvotes: 0