6

When implementing the __eq__ and __lt__ methods of a class, it is common practice to use tuples to group the values you wish to compare, like so:

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

    def __hash__(self):
        return hash((self.c, self.a, self.b))

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

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

However this uses the natural ordering of each key. What if I want to change, for example, how a sorts?

This is what I've come up with so far, and while it seems to work OK, I was wondering if there is a better way to go about it:

@total_ordering
class Foo(object):
    def __init__(self, a, b, c):
        self.a = MyA(a) # Note
        self.b = b
        self.c = c

    def __hash__(self):
        return hash((self.c, self.a, self.b))

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

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

class MyA(A):
    def __hash__(self):
        # ...

    def __eq__(self, other):
        # ...

    def __lt__(self, other):
        # ...

Subclassing A lets me define my custom ordering, and allows MyA to behave like a regular A in every other way which is nice, but it seems wasteful / needlessly verbose especially if I have to do this for multiple fields.

Edit: As per user1320237's answer below, this is what I've come up with:

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

    def __hash__(self):
        return hash((self.c, self.a, self.b))

    def __eq__(self, other):
        return (0, 0, 0) == (cmp(self.c, other.c),
                             cmpA(self.a, other.a),
                             cmp(self.b, other.b))

    def __lt__(self, other):
        return (0, 0, 0) > (cmp(self.c, other.c),
                            cmpA(self.a, other.a),
                            cmp(self.b, other.b))

def cmpA(a1, a2):
    # ...

(Note the > in __lt__ since cmp(x, y) returns -1 if x < y and __lt__ should return True)

0

2 Answers 2

6

if you want to order with list.sort() for example you can pass arguments to it:

Your code:

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

...
list.sort()

equivalent:

list.sort(key = lambda self: (self.c, self.a, self.b))

also equivalent:

list.sort(cmp = lambda self, other: \
                (self.c, self.a, self.b) < (other.c, other.a, other.b))

so if you want to sort your answers in different ways i would propose:

class Foo(object):
    @staticmethod
    def cmp_absoluteOrder(self, other):
        return (self.c, self.a, self.b) < (other.c, other.a, other.b)

    @staticmethod
    def cmp_otherOrder(self, other):
        return ...

    @staticmethod
    def cmp_combinedSort(cmpA, cmpB, cmpC):
        return lambda self, other: (0, 0, 0) < (cmpA(self.c, other.c), cmpA(self.a, other.a), cmpA(self.b, other.b), )

    def __hash__(self):
        return hash(self.c) ^ hashA(self.a) ^ hash(self.b)

...
list.sort(cmp = Foo.cmp_absoluteSorting)
list.sort(cmp = Foo.cmp_combinedSort(cmp, (lambda a1, a2: ...), cmp))

hashA = hash # or replace it if important # but the same a will retunrn the same hash

or something like this

Sign up to request clarification or add additional context in comments.

2 Comments

Oh, this is interesting! (0, 0, 0) < (cmpC(..), cmpA(...), cmpB(...)) might be exactly what I'm looking for. I should be able to put that into __lt__. Am I correct in assuming something like (0, 0, 0) == (...) should go in __eq__?
Ok, take a look at my edit; I've integrated your suggestion into my code. Now if I can figure out how to write __hash__ I'll mark this as the accepted answer.
1

If this is just a one off action, something like the following will work:

def custom_sorter(foo):
   """Takes Foo objects and gives a representation for sorting."""
   return (foo.c, A(foo.a), foo.b)

sorted(foo_list, key=custom_sorter)

If it is going to be done many times, you may either consider a class method on Foo that will be extensible and spit out prototypes similar to custom_sorter.

If you are using the same custom sorter many times, why not just make it part of the class?

You really just need to ask yourself why and do I really need to.

2 Comments

Should that be MyA(foo.a) in your example? If so, then moving the sorting into a seperate function doesn't help things since I still need the custom subclass. As you suggest I think it's best to keep the sort inside Foo, instead of having to pass key=... to sorted() every time I want to sort a bunch of Foos.
It can be whatever you like, it is custom.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.