Reputation: 5285
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
Suggestions on the best way to solve this?
Upvotes: 8
Views: 4119
Reputation: 251458
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.
Upvotes: 1
Reputation: 304355
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
Upvotes: 13
Reputation: 4728
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)]
Upvotes: 4