Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a typical instance of using '__rsub__' method in Python?

I'm teaching myself Python when I run into the __rsub__ method. While I can find explanation on the method in the official documentation:

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. 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.

I can't picture why the method is necessary and how exactly it is used in reality.

Would you kindly give me a typical environment where the method is useful?

like image 863
DaPanda Avatar asked Mar 02 '16 00:03

DaPanda


1 Answers

Basic example. You write your own int-like class:

class FooInt:
    ... other stuff elided ...

    def __sub__(self, other):
        if isinstance(other, FooInt):
            return self.__class__(self.intvalue - other.intvalue)
        elif isinstance(other, int):
            return self.__class__(self.intvalue - other)
        else:
            return NotImplemented

Now you have code that does:

FooInt(123) - 456

This works fine; Python sees FooInt on the left side, sees it has __sub__, and calls FooInt.__sub__(FooInt(123), 456) which returns without error, and we're good.

Next we see:

123 - FooInt(456)

Python tries calling int.__sub__(123, FooInt(456)), but int has no idea how to handle a FooInt, and returns NotImplemented; it has no idea that intvalue has a value it can use for this purpose. At this point, Python can't just call FooInt.__sub__(FooInt(456), 123) because it can't assume subtraction is commutative (and in fact, as in most numerical systems, subtraction is not commutative in this case, you can't just swap the right and left sides of the operator and get the correct result). This is why __rsub__ exists; it lets you check the other class for a means of handling the operation while telling it two things:

  1. It's on the right side of the binary operator (allowing it to handle commutativity correctly)
  2. The item on the left side did not know how to handle the operation, so this is the last chance to get it right

The second point is also very important. When implementing __xxx__ (the left hand operator) you want to be very conservative on the types you recognize. If you don't know for a fact you're working with a known concrete type with a known correct handler, you shouldn't try to handle unknown types; the other type might know how to do the operation correctly, so you return NotImplemented and let the other side handle it. When you're implementing __rxxx__, you're the last chance; the other guy didn't know what to do, so you should be liberal in what you accept and do your best if you have any means of handling it. You can see this in action in the Python docs on Implementing the arithmetic operations; the __xxx__ operations check for concrete types (for Fraction, it checks for Fraction, int, float, and complex), while the __rxxx__ operators check for general interfaces (numbers.Integral, numbers.Rational, numbers.Real, etc.).

I've glossed over one point that is relatively important: If the class on the right side is a subclass of the class on the left, it has its reflected __rxxx__ method called first; the assumption is that the subclass will have more information to correctly determine what to do, so it's given the first stab at performing the operation.

like image 175
ShadowRanger Avatar answered Nov 08 '22 23:11

ShadowRanger