Total_ordering 和 class 继承
Total_ordering and class inheritance
据我了解,functools
中的 total_ordering
装饰器预计不能很好地与从有序 class 继承的 classes 一起工作:它不会尝试定义比较函数,因为它们已经定义。
看这个例子:
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
添加到 class 定义来使 >
也能正常工作。
另一方面,如果我为 __le__
或 __ge__
添加类似的定义,相应的测试将失败并显示 (for __le__
):
TypeError: unorderable types: TestOrd() <= TestOrd()
这让我相信这不是解决问题的正确方法。
因此问题是:是否有正确的方法将 class 重新排序为 total_ordering?
(是的,我知道手动完成 total_ordering
的工作是微不足道的,而且我知道对于这个例子,定义一个无序的 namedtuple
也是微不足道的。)
对于您的示例,您可以通过引入不直接继承自 Test
的附加基 class 来解决问题:
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
的基数 class 的顺序很重要,TestOrdBase
必须在 Test
之前。
查看the implementation of 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)
(基于 previous implementation)。这只查看直接在 class 上定义的方法,导致装饰器覆盖继承的版本。这给出了您期望开始的结果:
False
False
True
True
True
True
False
False
请注意,您误解了定义:
__gt__ = lambda *_ : NotImplemented
有;它不会改变装饰器正在做的事情(在这种情况下什么也没有),它只是覆盖继承的版本并导致 >
在运行时被委托给其他方法。
据我了解,functools
中的 total_ordering
装饰器预计不能很好地与从有序 class 继承的 classes 一起工作:它不会尝试定义比较函数,因为它们已经定义。
看这个例子:
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
添加到 class 定义来使 >
也能正常工作。
另一方面,如果我为 __le__
或 __ge__
添加类似的定义,相应的测试将失败并显示 (for __le__
):
TypeError: unorderable types: TestOrd() <= TestOrd()
这让我相信这不是解决问题的正确方法。
因此问题是:是否有正确的方法将 class 重新排序为 total_ordering?
(是的,我知道手动完成 total_ordering
的工作是微不足道的,而且我知道对于这个例子,定义一个无序的 namedtuple
也是微不足道的。)
对于您的示例,您可以通过引入不直接继承自 Test
的附加基 class 来解决问题:
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
的基数 class 的顺序很重要,TestOrdBase
必须在 Test
之前。
查看the implementation of 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)
(基于 previous implementation)。这只查看直接在 class 上定义的方法,导致装饰器覆盖继承的版本。这给出了您期望开始的结果:
False
False
True
True
True
True
False
False
请注意,您误解了定义:
__gt__ = lambda *_ : NotImplemented
有;它不会改变装饰器正在做的事情(在这种情况下什么也没有),它只是覆盖继承的版本并导致 >
在运行时被委托给其他方法。