2

据我了解,total_ordering装饰器functools不能很好地与从有序类继承的类一起工作:它不会尝试定义比较函数,因为它们已经定义了。

看这个例子:

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__or添加类似的定义__ge__,则相应的测试会因 (for __le__) 而失败:

TypeError: unorderable types: TestOrd() <= TestOrd()

这使我相信这不是解决问题的正确方法。

因此,问题是:有没有一种适当的方法可以用 total_ordering 重新排序一个类?

(是的,我知道total_ordering手工完成 's 的工作是微不足道的,而且我知道对于这个例子,定义一个无序namedtuple的也是微不足道的。)

4

2 回答 2

4

对于您的示例,您可以通过引入一个不直接继承自的附加基类来解决该问题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.

于 2015-09-30T08:57:45.977 回答
2

查看的实现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

做; 它不会改变装饰器的工作(在这种情况下这没什么),它只是覆盖继承的版本并导致>在运行时被委托给其他方法。

于 2015-09-30T10:16:04.873 回答