In Python how would I compare with multiple values using custom keys?

python

Solution

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

Problem

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

Original source