Why does calling Python's 'magic method' not do type conversion like it would for the corresponding operator?

Question:

When I subtract a float from an integer (e.g. 1-2.0), Python does implicit type conversion (I think). But when I call what I thought was the same operation using the magic method __sub__, it suddenly does not anymore.

What am I missing here? When I overload operators for my own classes, is there a way around this other than explicitly casting input to whatever type I need?

a=1
a.__sub__(2.)
# returns NotImplemented
a.__rsub__(2.)
# returns NotImplemented
# yet, of course:
a-2.
# returns -1.0
Asked By: doppler

||

Answers:

a - b isn’t just a.__sub__(b). It also tries b.__rsub__(a) if a can’t handle the operation, and in the 1 - 2. case, it’s the float’s __rsub__ that handles the operation.

>>> (2.).__rsub__(1)
-1.0

You ran a.__rsub__(2.), but that’s the wrong __rsub__. You need the right-side operand’s __rsub__, not the left-side operand.


There is no implicit type conversion built into the subtraction operator. float.__rsub__ has to handle ints manually. If you want type conversion in your own operator implementations, you’ll have to handle that manually too.

Answered By: user2357112

@user2357112 already said it well but there’s nothing like an example.

class A:
   def __sub__(self, other):
       print('A.__sub__')
       if not isinstance(other, A):
           return NotImplemented
       return 0

   def __rsub__(self, other):
       print('A.__rsub__')
       if not isinstance(other, A):
           return NotImplemented
       return 0

class B:
   def __sub__(self, other):
       print('B.__sub__')
       if not isinstance(other, B):
           return NotImplemented
       return 0

a1 = A()
a2 = A()
b = B()

a1 - a2
A.__sub__
# 0

Objects a1 and a2 are compatible (both type A), a valid result is returned.

Next, consider,

b - a1
B.__sub__
A.__rsub__
# TypeError: unsupported operand type(s) for -: 'B' and 'A'

Objects b and a1 are not compatible. First, b.__sub__ is tried, which returns NotImplemented, so a1.__rsub__ is tried, which also returns NotImplemented. So a TypeError is raised.

Finally,

a1 - b
A.__sub__
# TypeError: unsupported operand type(s) for -: 'A' and 'B'

This time, a1.__sub__ is tried first, which returns NotImplemented. Now, since b.__rsub__ is not defined, a TypeError is raised.

Answered By: cs95