Friday 13 July 2018

Operator Overloading

Cited from the book "Python How to Program"

Operators are overloaded by writing a method definition as you normally would, except
that the method name corresponds to the Python special method for that operator. For
example, the method name __add__ overloads the addition operator (+). To use an operator
on an object of a class, the class must overload (i.e., define a special method for) that operator.

It is not possible to change the “arity” of an operator (i.e., the number of operands an
operator takes)—overloaded unary operators remain unary operators, and overloaded
binary operators remain binary operators. Operators + and - each have both unary and
binary versions; these unary and binary versions can be overloaded separately, using dif-
ferent method names. It is not possible to create new operators; only existing operators can
be overloaded.

The meaning of how an operator works on objects of built-in types cannot be changed
by operator overloading. The programmer cannot, for example, change the meaning of how
+ adds two integers. Operator overloading works only with objects of user-defined classes
or with a mixture of an object of a user-defined class and an object of a built-in type.

Overloading a binary mathematical operator (e.g., +, -, *) automatically overloads the
operator’s corresponding augmented assignment statement. For example, overloading an
addition operator to allow statements like

object2 = object2 + object1

implies that the += augmented assignment statement also is overloaded to allow statements
such as

object2 += object1

Although (in this case) the programmer does not have to define a method to overload the
+= assignment statement, such behavior also can be achieved by defining the method ex-
plicitly for that class.

A unary operator for a class is overloaded as a method that takes only the object reference
argument (self). When overloading a unary operator (such as ~) as a method, if object1 is an object of class Class, when the interpreter encounters the expression ~object1 the interpreter generates the call
object1.__invert__().

A binary operator or statement for a class is overloaded as a method with two arguments:
self and other. Later in this chapter, we will overload the + operator to indicate addi-
tion of two objects of class Rational. When overloading binary operator +, if y and z
are objects of class Rational, then y + z is treated as if y.__add__( z ) had been
written, invoking the __add__ method. If y is not an object of class Rational, but z is
an object of class Rational, then y + z is treated as if z.__radd__( y ) had been
written. The method is named __radd__, because the object for which the method exe-
cutes appears to the right of the operator. Usually, overloaded binary operator methods cre-
ate and return new objects of their corresponding class.

When overloading assignment statement += as a Rational method that accepts two
arguments, if y and z are objects of class Rational, then y += z is treated as if
y.__iadd__( z ) had been written, invoking the __iadd__ method. The method is
named __iadd__, because the method performs its operations “in-place” (i.e., the
method uses no extra memory to perform its behavior). Usually, this means that the method
performs any necessary calculations on the object reference argument (self), then returns
the updated reference.

What happens if we evaluate the expression y + z or the statement y += z, and only
y is an object of class Rational? In both cases, z must be coerced (i.e., converted) to an
object of class Rational, before the appropriate operator overloading method executes.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cited from A Guide to Python's Magic Methods

some_object + other
That was "normal" addition. The reflected equivalent is the same thing, except with the operands switched around:
other + some_object

Note that the object on the left hand side of the operator (other in the example) must not define (or return NotImplemented) for its definition of the non-reflected version of an operation. For instance, in the example, some_object.__radd__ will only be called if other does not define __add__.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cited from __radd__

Suppose you are implementing a class that you want to act like a number via operator overloading. So you implement __add__ in your class, and now expressions like myobj + 4 can work as you want and yield some result. This is because myobj + 4 is interpreted as myobj.__add__(4), and your custom method can do whatever it means to add 4 to your custom class.
However, what about an expression like 4 + myobj which is really (4).__add__(myobj)? The 4 is an instance of a Python built-in type and its __add__ method doesn't know anything about your new type, so it will return a special value NotImplemented. (The interpreter recognizes this special value coming from __add__ and raises a TypeError exception which kills your program, which is the behavior you'd actually see, rather than the special value being returned.)
It would suck for operator overloading if myobj + 4 was valid but 4 + myobj was invalid. That's arbitrary and restrictive — addition is supposed to be commutative. Enter __radd__. Python will first try (4).__add__(myobj), and if that returns NotImplemented Python will check if the right-hand operand implements __radd__, and if it does, it will call myobj.__radd__(4)rather than raising a TypeError. And now everything can proceed as usual, as your class can handle the case and implement your behavior, rather than the built-in type's __add__ which is fixed and doesn't know about your class.









No comments:

Post a Comment