我经常使用namedtuple类。我今天一直在想,是否有一种很好的方法可以为这样的类实现自定义排序,即使默认排序键不是namedtuple的第一个元素(然后是第二个、第三个等)。
我的第一直觉是实现__lt__
和__eq__
,让total_ordering
完成其余的工作(它填写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
但是:
def test_sortingB():
b1 = B(1, 2)
b2 = B(2, 1)
assert b2 < b1 # passes
assert b2 <= b1 # fails
哦,对。。。total_ordering
仅在缺少其他方法的情况下填充这些方法。由于tuple/namedtuple有这样的方法,total_ordering对我没有任何作用
所以我想我的选择是
- 停止使用namedtuple,只构建自己无聊的类,继续使用total_ordering
- 继续使用namedtuple并实现所有6种比较方法
- 继续使用namedtuple并插入一个排序值作为第一个字段。幸运的是,我没有太多的类实例,但通常我只是依靠字段的顺序来初始化它们,这可能很糟糕。也许这是个坏习惯
关于解决此问题的最佳方法的建议?
选项1。使用mixin并将total_ordering应用于该
@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
选项2。基于total_ordering
制作自己的装饰器,然后使用它来代替
如果正如您的问题所暗示的那样,您只对按备用键对命名元组进行排序感兴趣,为什么不将sort/sortedkey
参数与attrgetter
函数一起使用呢:
>>> 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)]
您可以更进一步,定义自己的排序函数:
>>> from functools import partial
>>> sortony = partial(sorted, key=attrgetter("y"))
>>> sortony([p1, p2])
[P(x=2, y=1), P(x=1, y=2)]
我的建议是按照您希望的字段排序顺序创建名称元组。您可能需要更改代码中创建值的部分(例如,将someTuple("name", 24)
更改为someTuple(24, "name")
,但通常情况下,值创建的位置比使用的位置少,所以这应该不会太大。这避免了编写所有比较方法的麻烦,而且作为奖励,还避免了一直调用这些自定义比较方法所带来的额外性能开销。