据我所知,functools
的total_ordering
装饰器不能很好地处理从有序类继承的类:它不会尝试定义比较函数,因为它们已经定义了。
请看下面的例子:
from functools import total_ordering
from collections import namedtuple
Test = namedtuple('Test',['a','b'])
@total_ordering
class TestOrd(Test):
def __lt__(self,other):
return self.b < other.b or self.b == other.b and self.a < other.a
x = TestOrd(a=1,b=2)
y = TestOrd(a=2,b=1)
print(x < y) # Expected: False
print(x <= y) # False
print(x > y) # True
print(x >= y) # True
print(y < x) # True
print(y <= x) # True
print(y > x) # False
print(y >= x) # False
在所有的测试中,只有涉及<
算子的测试给出了预期的结果。
我可以通过将__gt__ = lambda *_ : NotImplemented
添加到类定义中来获得>
的工作。另一方面,如果我为__le__
或__ge__
添加类似的定义,则相应的测试将失败(对于__le__
):
TypeError: unorderable types: TestOrd() <= TestOrd()
这让我相信这不是解决问题的正确方法。
因此,问题是:是否有一种合适的方法来重新排序一个类与total_ordering?(是的,我知道手工完成total_ordering
的工作是微不足道的,并且我知道对于本例,定义无序的namedtuple
也是微不足道的。)
对于您的示例,您可以通过引入一个不直接继承Test
的附加基类来解决这个问题:
Test = namedtuple('Test',['a','b'])
@total_ordering
class TestOrdBase:
def __lt__(self ,other):
return self.b < other.b or self.b == other.b and self.a < other.a
class TestOrd(TestOrdBase, Test):
pass
TestOrd
的基类顺序很重要,TestOrdBase
必须在Test
之前。
看看total_ordering
的实现,我们可以看到问题:
roots = [op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)]
仔细检查cls
上定义的版本不是从object
继承的版本,但任何其他继承的方法将被包含(即不被替换)。最小的调整是定义自己的副本(我称之为total_reordering
),而不是使用:
roots = set(cls.__dict__) & set(_convert)
(基于前面的实现)。这只查看直接定义在类上的方法,导致装饰器覆盖继承的版本。这将给出您期望以
开头的结果:False
False
True
True
True
True
False
False
注意你误解了定义:
__gt__ = lambda *_ : NotImplemented
做;它不会改变装饰器所做的事情(在这种情况下什么都没有),它只是覆盖继承的版本,并导致>
在运行时被委托给其他方法。