- rtshkmr's digital garden/
- Readings/
- Books/
- Fluent Python: Clear, Concise, and Effective Programming – Luciano Ramalho/
- Chapter 16. Operator Overloading/
Chapter 16. Operator Overloading
Table of Contents
There’s a value in allowing infix operators to handle any arbitrary type (not just primitive types):
- readable code that allows the non-primitive types to help with exactness of operations
This is why operator overloading is important.
Objectives:
- how to overload properly
- How an infix operator method should signal it cannot handle an operand
- Using duck typing or goose typing to deal with operands of various types
- The special behaviour of the rich comparison operators (e.g.,
=, >, <, etc.) - The default handling of augmented assignment operators such as
+=, and how to overload them
What’s New in This Chapter #
Operator Overloading 101 #
objective: interoperability of unary/infix/other operators with user defined objects
other operators includes
(),.,[]in pythonLANG_LIMITATIONS: Python Limitations on operator overloading (to protect us):
can’t change the meaning of the operators for built-in types
can’t create new operators, only can overload existing ones
some operators can’t be overloaded:
is,and,or,notthe bitwise versions can be overloaded though (so
$, |, ~)
Unary Operators #
random notes on these:
- usually
x =+x= but not in some cases - bitwise NOT is also
~x =-(x + 1)= if x is 2, then~x =-3=
- usually
easy to implement the appropriate unary function, just make the function pure and immutable
if the receiver itself is immutable, then we can just return self.
when is
xand+xnot equal?e.g. when precision matters. E.g. when using
Decimalyou can setxbased on a particular arithmetic precision, then change the precision and computex=+xand because the precisions will be different we will get back aFalsee.g. when using
collections.CounterTRICK: Unary
+produces a new Counter without zeroed or negative tallies. So we can use it to copy (and remove the negatives / zeros).
Overloading + for Vector Addition #
typically, sequences should support the
+operator for concatenation and*for repetition.when we have operands of diff types, we try to look for add or
r_addand take a best-effort approach:
support operations involving objects of different types, Python implements a special dispatching mechanism for the infix operator special methods:
If
ahas__add__, calla.__add__(b)and return result unless it’sNotImplemented.If a doesn’t have
__add__, or calling it returnsNotImplemented, check ifbhas__radd__, then callb.__radd__(a)and return result unless it’sNotImplemented.If
bdoesn’t have__radd__, or calling it returnsNotImplemented, raiseTypeErrorwith an unsupported operand types message.
GOTCHA:
NotImplementedis a singleton, not the same asNotImpelmentedErrorDo not confuse NotImplemented with NotImplementedError. The first, NotImplemented, is a special singleton value that an infix operator special method should return to tell the interpreter it cannot handle a given operand. In contrast, NotImplementedError is an exception that stub methods in abstract classes may raise to warn that subclasses must implement them.
note that if there’s any error, an overloaded operator function should return
NotImplementedinstead of other errors likeTypeError.this is so that the dispatch mechanism is not aborted prematurely
Overloading * for Scalar Multiplication #
Using @ as an Infix Operator #
- it’s been used for matrix multiplication, has both reflected version and an in-place version
- this is a useful goose typing example as well, both the ABCs implement the
__subclasshook__methods so we don’t need explicit subclassing / registration
Wrapping-Up Arithmetic Operators #
Rich Comparison Operators #
- differs from the arithmetic operators in these ways:
- same set of methods is used in forward and reverse operator calls (with the arguments changed as expected)
- for
!=and==, ifNotImplementedthen fallback toid()checks.
Augmented Assignment Operators #
- for immutable objects, the augment assignment operators are just syntactic sugar for the expanded version, that’s why they return new objects
- for mutable objects, depends on whether we implemented the dunder methods or not
- Very important: augmented assignment special methods of mutable objects must return self. That’s what users expect.
- IDIOM: In general, if a forward infix operator method (e.g., mul) is
designed to work only with operands of the same type as self, it’s useless to implement the corresponding reverse method (e.g.,=_rmul_=) because that, by definition, will only be invoked when dealing with an operand of a different type.
Chapter Summary #
when handling mixed operands, we have 2 choices:
use duck typing:
this is useful and flexible but the error messages may be less useful or even misleading
use goose typing:
this is useful as a compromise between flexibility and safety beacuse existing / future user-defined types can be declared as actual or virtual subclasses of an ABC
Also if ABC implements the
__subclasshook__then it’s even more convenient because no need explicit subclassing or registration.
the in place operator is usually more flexible than its infix operator in terms of type strictness.