65

The following piece of code

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def dispc(self):
        return ('(' + str(self.x) + ',' + str(self.y) + ')')

    def __cmp__(self, other):
        return ((self.x > other.x) and (self.y > other.y))

works fine in Python 2, but in Python 3 I get an error:

>>> p=point(2,3)
>>> q=point(3,4)
>>> p>q
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: point() > point()

It only works for == and !=.

nbro
  • 13,796
  • 25
  • 99
  • 185

3 Answers3

92

You need to provide the rich comparison methods for ordering in Python 3, which are __lt__, __gt__, __le__, __ge__, __eq__, and __ne__. See also: PEP 207 -- Rich Comparisons.

__cmp__ is no longer used.


More specifically, __lt__ takes self and other as arguments, and needs to return whether self is less than other. For example:

class Point(object):
    ...
    def __lt__(self, other):
        return ((self.x < other.x) and (self.y < other.y))

(This isn't a sensible comparison implementation, but it's hard to tell what you were going for.)

So if you have the following situation:

p1 = Point(1, 2)
p2 = Point(3, 4)

p1 < p2

This will be equivalent to:

p1.__lt__(p2)

which would return True.

__eq__ would return True if the points are equal and False otherwise. The other methods work analogously.


If you use the functools.total_ordering decorator, you only need to implement e.g. the __lt__ and __eq__ methods:

from functools import total_ordering

@total_ordering
class Point(object):
    def __lt__(self, other):
        ...

    def __eq__(self, other):
        ...
nbro
  • 13,796
  • 25
  • 99
  • 185
Jesse Dhillon
  • 7,561
  • 32
  • 34
  • 1
    Now you need to define `__hash__` to use your object in a set or as a dict key. – Eryk Sun Nov 26 '11 at 08:38
  • 2
    Thanks for the tip about using `functools.total_ordering`! I just tried it out on Python 3.6, and I only needed to define the `__lt__(self, other)` method. (Probably because if `a < b` and `b < a` are both false, then it follows that `a == b` must be true.) – J-L May 31 '19 at 00:36
  • it actually "must define at least one ordering operation: < > <= >=" - by error message – Xanlantos May 22 '20 at 02:42
15

This was a major and deliberate change in Python 3. See here for more details.

  • The ordering comparison operators (<, <=, >=, >) raise a TypeError exception when the operands don’t have a meaningful natural ordering. Thus, expressions like 1 < '', 0 > None or len <= len are no longer valid, and e.g. None < None raises TypeError instead of returning False. A corollary is that sorting a heterogeneous list no longer makes sense – all the elements must be comparable to each other. Note that this does not apply to the == and != operators: objects of different incomparable types always compare unequal to each other.
  • builtin.sorted() and list.sort() no longer accept the cmp argument providing a comparison function. Use the key argument instead. N.B. the key and reverse arguments are now “keyword-only”.
  • The cmp() function should be treated as gone, and the __cmp__() special method is no longer supported. Use __lt__() for sorting, __eq__() with __hash__(), and other rich comparisons as needed. (If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b).)
Jorn Vernee
  • 28,972
  • 3
  • 72
  • 84
Ned Deily
  • 81,219
  • 16
  • 124
  • 150
9

In Python3 the six rich comparison operators

__lt__(self, other) 
__le__(self, other) 
__eq__(self, other) 
__ne__(self, other) 
__gt__(self, other) 
__ge__(self, other) 

must be provided individually. This can be abbreviated by using functools.total_ordering.

This however turns out rather unreadable and unpractical most of the time. Still you have to put similar code pieces in 2 funcs - or use a further helper func.

So mostly I prefer to use the mixin class PY3__cmp__ shown below. This reestablishes the single __cmp__ method framework, which was and is quite clear and practical in most cases. One can still override selected rich comparisons.

Your example would just become:

 class point(PY3__cmp__):
      ... 
      # unchanged code

The PY3__cmp__ mixin class:

PY3 = sys.version_info[0] >= 3
if PY3:
    def cmp(a, b):
        return (a > b) - (a < b)
    # mixin class for Python3 supporting __cmp__
    class PY3__cmp__:   
        def __eq__(self, other):
            return self.__cmp__(other) == 0
        def __ne__(self, other):
            return self.__cmp__(other) != 0
        def __gt__(self, other):
            return self.__cmp__(other) > 0
        def __lt__(self, other):
            return self.__cmp__(other) < 0
        def __ge__(self, other):
            return self.__cmp__(other) >= 0
        def __le__(self, other):
            return self.__cmp__(other) <= 0
else:
    class PY3__cmp__:
        pass
kxr
  • 3,892
  • 41
  • 27
  • 1
    I liked the way you wrote the operators. However, please explain this statement (a > b) - (a < b). It took me a while to understand this. – theBuzzyCoder Jun 07 '17 at 04:04
  • 5
    @theBuzzyCoder `bool` is just a subclass of `int`, so `True` and `False` are basically 1 and 0, respectively. Since `cmp` returns a negative value if its first argument is less than its second argument, zero if the arguments are equal, or a positive value otherwise, you can see that `False - False == 0`, `True - False == 1`, and `False - True == -1` provide the correct return values for `cmp`. – chepner Jul 14 '17 at 16:05