19

I'm doing a project in Python (3.2) for which I need to compare user defined objects. I'm used to OOP in Java, where one would define a compareTo() method in the class that specifies the natural ordering of that class, as in the example below:

public class Foo {
    int a, b;

    public Foo(int aa, int bb) {
        a = aa;
        b = bb;
    }

    public int compareTo(Foo that) {
        // return a negative number if this < that
        // return 0 if this == that
        // return a positive number if this > that

        if (this.a == that.a) return this.b - that.b;
        else return this.a - that.a;
    }
}

I'm fairly new to classes/objects in Python, so I'd like to know what is the "pythonic" way to define the natural ordering of a class?

astay13
  • 6,857
  • 10
  • 41
  • 56

2 Answers2

26

You can implement the special methods __lt__, __gt__ etc. to implement the default operators for custom types. See more about them in the language reference.

For example:

class Foo:
    def __init__ (self, a, b):
        self.a = a
        self.b = b

    def __lt__ (self, other):
        if self.a == other.a:
            return self.b < other.b
        return self.a < other.b

    def __gt__ (self, other):
        return other.__lt__(self)

    def __eq__ (self, other):
        return self.a == other.b and self.b == other.b

    def __ne__ (self, other):
        return not self.__eq__(other)

Or as said by stranac in the comments, you can use the total_ordering decorator to save some typing:

@functools.total_ordering
class Foo:
    def __init__ (self, a, b):
        self.a = a
        self.b = b

    def __lt__ (self, other):
        if self.a == other.a:
            return self.b < other.b
        return self.a < other.b

    def __eq__ (self, other):
        return self.a == other.b and self.b == other.b
poke
  • 369,085
  • 72
  • 557
  • 602
  • 3
    You could also just define `__lt__` and `__eq__` and use the `functools.total_ordering` decorator. – stranac Jun 26 '12 at 21:16
  • 1
    In Python 2 `__gt__` is automatically `not __lt__` if not provided (just tested it). Strange that the documentation says otherwise. Someone want to test it on Python 3, maybe @stranac ? – schlamar Jun 26 '12 at 21:28
  • You are right, but it might be possible that the documentation actually only refers to the inverse, i.e. `not (a < b) == (a >= b)` (where the latter will raise an exception if not defined), so I guess it does swap the parameters if possible. – poke Jun 26 '12 at 21:33
  • @ms4py: I'm guessing the docs are just saying that if `__gt__` is `False`, that doesn't necessarily mean `__lt__` is `True`. They could be defined in a way that both are `True`/`False`. – stranac Jun 26 '12 at 21:52
7

Python has a similar function: __cmp__().

I now see you're asking about Python 3. Their "whats new" suggests:

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).)

So it seems you could always do something like

def compareTo(self, that):
    return ((self > that) - (self < that))

or

@classmethod
def compare(cls, a, b):
    return ((a > b) - (a < b))

after implementing __gt__() and __lt__().

Which you would then use like:

f1 = Foo(1,1)
f2 = Foo(2,2)

f1.compareTo(f2)
Foo.compare(f1,f2)

This would give you equivalent functionality.

jedwards
  • 29,432
  • 3
  • 65
  • 92