c++boost.gif (8819 bytes)

Rich Comparisons


In Python versions up to and including Python 2.0, support for implementing comparisons on user-defined classes and extension types was quite simple. Classes could implement a __cmp__ method that was given two instances of a class as arguments, and could only return 0 if they were equal or +1 or -1 if they were not. The method could not raise an exception or return anything other than an integer value. In Python 2.1, Rich Comparisons were added (see PEP 207). Python classes can now individually overload each of the <, <=, >, >=, ==, and != operations.

For more detailed information, search for "rich comparison" here.

Boost.Python supports both automatic overloading and manual overloading of the Rich Comparison operators. The compile-time support is independent of the Python version that is used when compiling Boost.Python extension modules. That is, op_lt for example can always be used, and the C++ operator< will always be bound to the Python method __lt__. However, the run-time behavior will depend on the Python version.

With Python versions before 2.1, the Rich Comparison operators will not be called by Python when any of the six comparison operators (<, <=, ==, !=, >, >=) is used in an expression. The only way to access the corresponding methods is to call them explicitly, e.g. a.__lt__(b). Only with Python versions 2.1 or higher will expressions like a < b work as expected.

To support Rich Comparisions, the Python C API was modified between Python versions 2.0 and 2.1. A new slot was introduced in the PyTypeObject structure: tp_richcompare. For backwards compatibility, a flag (Py_TPFLAGS_HAVE_RICHCOMPARE) has to be set to signal to the Python interpreter that Rich Comparisions are supported by a particular type. There is only one flag for all the six comparison operators. When any of the six operators is wrapped automatically or manually, Boost.Python will set this flag. Attempts to use comparison operators at the Python level that are not defined at the C++ level will then lead to an AttributeError when the Python 2.1 (or higher) interpreter tries, e.g., a.__lt__(b). That is, in general all six operators should be supplied. Automatically wrapped operators and manually wrapped operators can be mixed. For example:

    boost::python::class_builder<code> py_code(this_module, "code");

    py_code.def(boost::python::constructor<>());
    py_code.def(boost::python::constructor<int>());
    py_code.def(boost::python::operators<(  boost::python::op_eq
                                          | boost::python::op_ne)>());
    py_code.def(NotImplemented, "__lt__");
    py_code.def(NotImplemented, "__le__");
    py_code.def(NotImplemented, "__gt__");
    py_code.def(NotImplemented, "__ge__");
NotImplemented is a simple free function that (currently) has to be provided by the user. For example:
  boost::python::ref
  NotImplemented(const code&, const code&) {
    return
    boost::python::ref(Py_NotImplemented, boost::python::ref::increment_count);
  }
See also:
© Copyright Nicholas K. Sauter & Ralf W. Grosse-Kunstleve 2001. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Updated: July 2001