I am currently trying to understand where the difference between using a+b
and a.__add__(b)
is when it comes to custom classes. There are numerous websites that say that using the '+'-operator results in using the special method __add__
- which is fine so far.
But when i run the following example I get two different results.
class C:
def __add__(self, other):
print("C.__add__", self, other)
return "result"
def __radd__(self, other):
print("C.__radd__", self, other)
return "reversed result"
c = C()
print(1+c)
print()
print(1 .__add__(c))
print(int.__add__(1,c))
Result:
C.__radd__ <C object at 0x7f60b92e9550> 1
reversed result
NotImplemented
NotImplemented
Now from what I understood, when executing 1+c
Python checks/executes the int __add__
method - finds that there is no implementation for adding int and C objects - returns NotImplemented - which lets Python know to check object C for __radd__
and executes the code within.
Why does 1+c
result in executing the __radd__
code but the other two version are just returning NotImplemented
without checking __radd__
??
a+b
is equivalent to import operator; operator.add(a,b)
. It starts by calling a.__add__(b)
and then, if necessary, b.__radd__(a)
. But ifsubclass(type(b), type(a))
, then b.__radd__(a)
is called first.
Based on the docs on "special" methods:
Regarding __add__()
:
__add__()
is called to implement the binary arithmetic "+" operation. For instance, to evaluate the expression x + y, where x is an instance of a class that has an__add__()
method,x.__add__(y)
is called.If one of those methods does not support the operation with the supplied arguments, it should return NotImplemented.
Regarding __radd__()
:
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, where y is an instance of a class that has an
__radd__()
method,y.__radd__(x)
is called ifx.__add__(y)
returns NotImplemented.If the right operand’s type is a subclass of the left operand’s type and that subclass provides the reflected method for the operation, this method will be called before the left operand’s non-reflected method. This behavior allows subclasses to override their ancestors’ operations.
Explanation with the examples based on the behaviour:
Case 1:
>>> print 1+c
('C.__radd__', <__main__.C instance at 0x7ff5631397a0>, 1)
reversed result
These functions radd
are only called if the left operand does not support the corresponding operation and the operands are of different types. In this case, 1
does not support addition of the class hence, it falls back to the __radd__()
function of the C
class. In case __radd__
was not implement in C()
class, it would have fallen back to __add__()
Case2:
>>> 1 .__add__(c)
NotImplemented
>>> c .__add__(1)
('C.__add__', <__main__.C instance at 0x7ff563139830>, 1)
'result'
1 .__add__(c)
gives NotImplemented
as 1
is of int
type and add
of int
class do not supports add
with C class object. But c .__add(1)
run because C()
class supports that.
Case 3:
>>> int.__add__(1, c)
NotImplemented
>>> C.__add__(c, 1)
('C.__add__', <__main__.C instance at 0x7ff5610add40>, 1)
'result'
Similar to case 2
. But here, the call is made via class with first argument as object of that class. Behaviour would be same.
Case 4:
>>> int.__add__(c, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__add__' requires a 'int' object but received a 'instance'
>>> C.__add__(1, c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method __add__() must be called with C instance as first argument (got int instance instead)
Vice-versa of case 3
. As is cleared from the stack-trace, __add__
expected the object of the calling class as the first argument, failing which resulted in exception.
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