2015-09-30 5 views
2

Как я понял, декодер total_ordering от 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 «s вручную тривиально, и я знаю, что для этого примера, определяя неупорядоченный namedtuple тривиален тоже.)

+0

* «Есть ли способ переупорядочить класс с total_ordering?«* - нет, как вы указываете, это заменит только те методы, которые еще не определены, поэтому класс наследования должен переопределить все из них явно, чтобы изменить порядок. – jonrsharpe

+0

@jonrsharpe Может быть, способ отметить какой-то метод как неопределенный? , это можно сделать для '>' например ... –

+0

Я не думаю, что вы понимаете, что это делает, это не влияет на 'total_ordering' (который ищет * существование *, а не * реализацию *). вы удаляете декоратор, поведение не изменится. Я полагаю, вы могли бы создать новый декоратор оформления, который будет выглядеть только в методах, определенных непосредственно на этом конкретном классе (проверьте в '__dict__', а не' dir'). – jonrsharpe

ответ

3

Для примера, вы можете решить проблему путем введения дополнительного базового класса, который непосредственно не унаследовать от 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.

+0

Ага, спасибо! Если даже можно сделать более общий «OrdBase», делегирующий сравнение функции, определенной в «TestOrd» позже (я думаю, что предпочтение будет зависеть от того, сколько таких классов нам нужно будет определить и сколько еще идет в подкласс). Дак-типизация в лучшем виде! –

+0

Обычно это относится к классу, подобному этому, как к «смешению», хотя в этом случае он довольно тесно связан с реализацией класса, в который вы его смешиваете. – jonrsharpe

2

Глядя на 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). Это рассматривает только методы, непосредственно определенные в классе, заставляя декоратор переопределять унаследованные версии. Это дает результаты, которые вы ожидали, чтобы начать с:

False 
False 
True 
True 
True 
True 
False 
False 

Обратите внимание, что вы не поняли, что определяющим:

__gt__ = lambda *_ : NotImplemented 

делает; он не меняет того, что делает декоратор (что в данном случае ничего не значит), он просто переопределяет унаследованную версию и вызывает >, чтобы получить делегирование другим методам во время выполнения.