Total_ordering和类继承



据我所知,functoolstotal_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

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

相关内容

  • 没有找到相关文章

最新更新