Pergunta

I use namedtuple classes a lot. I have been thinking today if there is a nice way to implement custom sorting for such a class, i.e. make the default sort key not the first element (then second, third, etc) of the namedtuple.

My first instinct was to implement __lt__ and __eq__ and let total_ordering do the rest (it fills out le, ne, gt, ge):

from collections import namedtuple
from functools import total_ordering


@total_ordering
class B(namedtuple('B', 'x y')):
    def __lt__(self, other):
        return self.y < other.y

However:

def test_sortingB():
    b1 = B(1, 2)
    b2 = B(2, 1)
    assert b2 < b1  # passes
    assert b2 <= b1  # fails

oh, right... total_ordering only fills out the other methods if they are missing. Since tuple/namedtuple has such methods, total_ordering isn't doing anything for me.

So I guess my options are

  1. stop using namedtuple and just build my own boring class, keep using total_ordering
  2. keep using namedtuple and implement all 6 comparison methods
  3. keep using namedtuple and insert a sort value as the first field. Luckily I don't have too many instances of the class, but usually I just rely on the order of the fields to initialise them which could be nasty. Maybe that is a bad habit.

Suggestions on the best way to solve this?

Foi útil?

Solução

OPTION 1. Use a mixin and apply the total_ordering to that

@total_ordering
class B_ordering(object):
    __slots__ = ()                 # see Raymond's comment
    def __lt__(self, other):
        return self.y < other.y

class B(B_ordering, namedtuple('B', 'x y')):
    pass

OPTION 2. Make your own decorator based on total_ordering and just use that instead

Outras dicas

If, as your question implies, your interest is only in sorting namedtuples by an alternate key, why not use the sort/sorted key argument with the attrgetter function:

>>> from collections import namedtuple
>>> from operator import attrgetter
>>> P = namedtuple("P", "x y") 
>>> p1 = P(1, 2)
>>> p2 = P(2, 1)
>>> sorted([p1, p2], key=attrgetter("y"))
[P(x=2, y=1), P(x=1, y=2)]

You can go even further and define your own sort function:

>>> from functools import partial
>>> sortony = partial(sorted, key=attrgetter("y"))
>>> sortony([p1, p2])
[P(x=2, y=1), P(x=1, y=2)]

My advice would be to create your namedtuple with the fields in the order you want them to be sorted by. You might have to change the parts of your code where you create your values (e.g., change someTuple("name", 24) to someTuple(24, "name"), but generally values are created in fewer places than they're used in, so this shouldn't be too a big a deal. This avoids the hassle of writing all the comparison methods, and as a bonus also avoids the additional performance overhead of having those custom comparison methods called all the time.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top