Skip to main content
  1. Readings/
  2. Books/
  3. Fluent Python: Clear, Concise, and Effective Programming – Luciano Ramalho/

Chapter 16. Operator Overloading

··802 words·4 mins
  • 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:

    1. how to overload properly
    2. How an infix operator method should signal it cannot handle an operand
    3. Using duck typing or goose typing to deal with operands of various types
    4. The special behaviour of the rich comparison operators (e.g., =, >, <, etc.)
    5. 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 python

  • LANG_LIMITATIONS: Python Limitations on operator overloading (to protect us):

    1. can’t change the meaning of the operators for built-in types

    2. can’t create new operators, only can overload existing ones

    3. some operators can’t be overloaded: is, and, or, not

      the 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=
  • 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 x and +x not equal?

    • e.g. when precision matters. E.g. when using Decimal you can set x based on a particular arithmetic precision, then change the precision and compute x=+x and because the precisions will be different we will get back a False

    • e.g. when using collections.Counter

      TRICK: 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_add and take a best-effort approach:

    support operations involving objects of different types, Python implements a special dispatching mechanism for the infix operator special methods:

    1. If a has __add__, call a.__add__(b) and return result unless it’s NotImplemented.

    2. If a doesn’t have __add__, or calling it returns NotImplemented, check if b has __radd__, then call b.__radd__(a) and return result unless it’s NotImplemented.

    3. If b doesn’t have __radd__, or calling it returns NotImplemented, raise TypeError with an unsupported operand types message.

  • GOTCHA: NotImplemented is a singleton, not the same as NotImpelmentedError

    Do 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 NotImplemented instead of other errors like TypeError.

    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:
    1. same set of methods is used in forward and reverse operator calls (with the arguments changed as expected)
    2. for != and ==, if NotImplemented then fallback to id() 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.

Further Reading #