-1

I previously asked the same question of why 10 .__add__(5.5) and 10. __radd__ (5.5) return NotImplemented but the question was closed due to possible duplicates.

The answer of one of the "duplicates" state that:

a + b does not directly translate to a.__add__(b). It also tries b.__radd__(a) if a.__add__ doesn't exist or returns NotImplemented.

That answer suggests that either __add__ or __radd__ should work, but neither __add__ nor __radd__ really work as demonstrated in my code above.

The answer of the other "duplicate" state that:

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)

Again, neither __add__ nor __radd__ do the job. So, those answers are basically paraphrasing the Python docs on special methods which state that when a + operator occurs __add__ is called first and then __radd__ if __add__ was not successful. What happens when __radd__ is not successful either?

multigoodverse
  • 7,638
  • 19
  • 64
  • 106
  • 4
    What are you talking about, `__radd__` is successful, it calls `__radd__` on the *argument* not on itself. So, you can think of `10 + 5.5` first calling `10 .__add__(5.5)` and getting `NotImplemented`, then it calls `5.5.__radd__(10)` – juanpa.arrivillaga Jan 08 '20 at 12:05
  • So *read this again* **carefully**: "`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)`" Note the order of `a` and `b` – juanpa.arrivillaga Jan 08 '20 at 12:10
  • @juanpa.arrivillaga I totally missed that when `__radd__` is called by "+", the operands are reversed. Yet, understand why '__add__' works on `float.__add__(int)` and not in `int.__add__(float)`. Why is `float` given more priority? – multigoodverse Jan 08 '20 at 13:51

1 Answers1

1

I think you didn't realize that the operators are swapped for the reverse operation. It's b.__radd__(a) - it calls __radd__ on the other operator!

So because 10 .__add__(5.5) returns NotImplemented it calls 5.5 .__radd__(10) (which returns 15.5) not 10. __radd__ (5.5). If the __radd__ on the swapped operators also returns NotImplemented the Python would raise an appropriate TypeError.

This snippet demonstrates how the methods are called for + (and what happens if both return NotImplemented):

class T:
    def __init__(self, value):
        self._value = value

    def __repr__(self):
        return f"{type(self).__name__}({self._value})"

    def __add__(self, other):
        print(f'called {self!r}.__add__({other!r})')
        return NotImplemented

    def __radd__(self, other):
        print(f'called {self!r}.__radd__({other!r})')
        return NotImplemented

class U(T):
    ...

>>> T(1) + U(2)
called T(1).__add__(U(2))
called U(2).__radd__(T(1))

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
---> 19 T(1) + U(2)
TypeError: unsupported operand type(s) for +: 'T' and 'U'

Because __radd__ is only called if the class of the operands differ I needed to create a second class in this example.

However you really shouldn't call special methods directly, if you need a function to perform such an operation you could use operator.add in your case.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Thanks for the nice trick to demonstrate how `__add__` and `__radd__` are called. I understand that if the class operands are not of the same type, `__radd__` gets called for help. However, I think I am still missing something - why does `__add__` work in `1.5__add__(10)`and not in `10 .__add__(1.5)` – multigoodverse Jan 08 '20 at 13:46
  • In other words, why is the float given more priority? Why does a float allow itself to be added an integer and an integer doesn't allow a float to be added. – multigoodverse Jan 08 '20 at 13:56
  • @multigoodverse That's just an implementation detail of Python. The `__add__` and `__radd__` of integers know about other integers. The `__add__` and `__radd__` of floats know about floats **and** integers. I think it's because integers represent a subset of float, while float represents a subset of complex, and so on. It's just important that one class knows how to deal with the other one to work properly. Why duplicate the int+float operations in float and int if (by the data model definition) it suffices that float knows about it. – MSeifert Jan 08 '20 at 14:50
  • @multigoodverse In case you're interested about the rationale for that see [PEP-3141](https://www.python.org/dev/peps/pep-3141/) and the [`numbers`](https://docs.python.org/library/numbers.html) module. – MSeifert Jan 08 '20 at 14:54