Peeyush Kushwaha
Peeyush Kushwaha

Reputation: 3623

To make objects of a custom class comparable, is it enough to define just a few of the members in `__eq__` and `__lt__` family?

Let's say I have a class, members of which I want to compare with the usual operators ==, <, <=, >, and >=.

As I understand, this could be accomplished by initializing defining magic method __cmp__(a, b) which returns -1 (a < b), 0 (a == b), or 1 (a > b).

It seems like __cmp__ was deprecated since Python 3 in favor of defining __eq__, __lt__, __le__, __gt__, and _ge__ methods separately.

I defined __eq__ and __lt__ assuming that the default values for __le__ would look something like return a == b or a < b. It seems to be not the case with the following class:

class PQItem:
    def __init__(self, priority, item):
        self.priority = priority
        self.item = item

    def __eq__(self, other):
        return isinstance(other, PQItem) and self.priority == other.priority

    def __lt__(self, other):
        return isinstance(other, PQItem) and self.priority < other.priority

class NotComparable:
    pass

x = NotComparable()
y = NotComparable()
# x < y gives error

And I get this outcome:

>>> PQItem(1, x) == PQItem(1, y)
True
>>> PQItem(1, x) < PQItem(1, y)
False
>>> PQItem(1, x) <= PQItem(1, y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: PQItem() <= PQItem()

This has left me to conclude that I'll have to define all the comparison magic methods manually to make a class comparable. Is there any better way?

Why has __cmp__ been deprecated? That seems like a much nicer way to deal with it

Upvotes: 3

Views: 1166

Answers (1)

blhsing
blhsing

Reputation: 106995

For two objects a and b, __cmp__ requires that one of a < b, a == b, and a > b is true. But that might not be the case: consider sets, where it's very common that none of those are true, e.g. {1, 2, 3} vs {4, 5, 6}.

So __lt__ and the likes were introduced. But that left Python with two separate ordering mechanisms, which is kind of ridiculous, so the less flexible one was removed in Python 3.

You don't actually have to implement all six comparison methods. You can use the functools.total_ordering class decorator to help define the rest of the magic comparison methods:

from functools import total_ordering
@total_ordering
class PQItem:
    def __init__(self, priority, item):
        self.priority = priority
        self.item = item

    def __eq__(self, other):
        return isinstance(other, PQItem) and self.priority == other.priority

    def __lt__(self, other):
        return isinstance(other, PQItem) and self.priority < other.priority

Upvotes: 6

Related Questions