Method resolution and the reflected operator concept

The arithmetic operators (+, -, *, /, //, %, **, and so on) all map to special method names. When we provide an expression such as 355+113, the generic + operator will be mapped to a concrete __add__() method of a specific numeric class. This example will turn out to be evaluated as though we had written 355.__add__(113). The simplest rule is that the left-most operand determines the class of the operator being used.

But wait, there's more! When we have an expression with mixed types, Python may end up with two available implementations of the special method, one in each class. Consider 7-0.14 as an expression. Using the left-side int class, this expression will be attempted as 7.__sub__(0.14). This involves an unpleasant complexity, since the argument to an int operator is a float value 0.14 and converting float to int could potentially lose precision. Converting up the tower of types (from int toward complex) won't lose precision. Converting down the tower of types implies a potential loss of precision.

Using the right-hand side float version, however, this expression will be attempted as 0.14.__rsub__(7). In this case, the argument to a float operator is an int value 7; converting int up the tower to float doesn't (generally) lose precision. (A truly giant int value can lose precision; however, that's a technical quibble, not a general principle.)

The __rsub__() operation is called reflected subtraction. The X.__sub__(Y) operation is the expected subtraction, and the A.__rsub__(B) operation is the reflected subtraction; the implementation method in the latter comes from the right-hand side operand's class. So far, we've seen the following two rules:

  • Rule one: try the left-hand side operand's class first. If that works, good. If the operand returns NotImplemented as a value, then use rule two.
  • Rule two: try the right-hand side operand with the reflected operator. If that works, good. If it returns NotImplemented, then it really is not implemented, so an exception must be raised.

The notable exception is when the two operands happen to have a subclass relationship.

The following additional rule applies before the first pair rules as a special case:

  • If the right operand is a subclass of the left, and the subclass defines the reflected special method name for the operator, then the subclass reflected operator will be tried. This allows a subclass override to be used, even if the subclass operand is on the right-hand side of the operator.
  • Otherwise, use rule one and try the left side.

Imagine we wrote a subclass of float, called MyFloat. In an expression such as  2.0-MyFloat(1), the right operand is of a subclass of the left operand's class. Because of this subclass relationship, MyFloat(1).__rsub__(2.0) will be tried first. The point of this rule is to give precedence to the subclass.

This means that a class that will do implicit coercion from other types must implement the forward as well as the reflected operators. When we implement or extend a numeric type, we must work out the conversions that our type is able to do.

In the next section, we'll take a look at the arithmetic operator's special methods.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset