O Python 2.x possui duas maneiras de sobrecarregar os operadores de comparação __cmp__
ou os "operadores de comparação avançados", como __lt__
. Diz-se que as sobrecargas de comparação avançada são as preferidas, mas por que isso acontece?
Os operadores de comparação avançados são mais simples de implementar cada um, mas você deve implementar vários deles com lógica quase idêntica. No entanto, se você pode usar a cmp
ordenação interna e de tupla, __cmp__
torna - se bastante simples e preenche todas as comparações:
class A(object):
def __init__(self, name, age, other):
self.name = name
self.age = age
self.other = other
def __cmp__(self, other):
assert isinstance(other, A) # assumption for this example
return cmp((self.name, self.age, self.other),
(other.name, other.age, other.other))
Essa simplicidade parece atender às minhas necessidades muito melhor do que sobrecarregar todas as 6 (!) Comparações ricas. (No entanto, você pode baixá-lo para "apenas" 4 se confiar no "argumento trocado" / comportamento refletido, mas isso resulta em um aumento líquido de complicações, na minha humilde opinião.)
Existem armadilhas imprevistas das quais preciso estar ciente se apenas sobrecarregar __cmp__
?
Eu entendo a <
, <=
, ==
, etc. operadores podem ser sobrecarregados para outros fins, e pode retornar qualquer objeto que eles gostam. Não estou perguntando sobre os méritos dessa abordagem, mas apenas sobre as diferenças ao usar esses operadores para comparações no mesmo sentido que significam para números.
Atualização: Como Christopher apontou , cmp
está desaparecendo no 3.x. Existem alternativas que tornam a implementação de comparações tão fácil quanto as anteriores __cmp__
?
fonte
Respostas:
Sim, é fácil implementar tudo em termos de, por exemplo,
__lt__
uma classe mixin (ou uma metaclasse, ou um decorador de classe, se o seu gosto funcionar dessa forma).Por exemplo:
Agora sua classe pode definir justamente
__lt__
e multiplicar herdar de ComparableMixin (depois de quaisquer outras bases de que precise, se houver). Um decorador de classe seria bastante semelhante, apenas inserindo funções semelhantes como atributos da nova classe que está decorando (o resultado pode ser microscopicamente mais rápido em tempo de execução, a um custo igualmente minuto em termos de memória).Claro, se sua classe tem uma maneira particularmente rápida de implementar (por exemplo)
__eq__
e__ne__
deve defini-los diretamente para que as versões do mixin não sejam usadas (por exemplo, é o caso dedict
) - na verdade,__ne__
pode ser bem definido para facilitar isso como:mas no código acima eu queria manter a simetria agradável de usar apenas
<
;-). Quanto ao porquê__cmp__
tinha que ir, desde que nós fizemos têm__lt__
e amigos, por que manter outra, maneira diferente de fazer exatamente a mesma coisa ao redor? É muito peso morto em cada tempo de execução do Python (Classic, Jython, IronPython, PyPy, ...). O código que definitivamente não terá bugs é o código que não está lá - daí o princípio do Python de que deve haver idealmente uma maneira óbvia de executar uma tarefa (C tem o mesmo princípio na seção "Espírito de C" do o padrão ISO, aliás).Isso não significa que vamos sair do nosso caminho para proibir as coisas (por exemplo, quase equivalência entre mixins e decoradores de classe para alguns usos), mas definitivamente não significa que nós não gostam de transportar cerca de código nos compiladores e / ou tempos de execução que existem de forma redundante apenas para oferecer suporte a várias abordagens equivalentes para executar exatamente a mesma tarefa.
Edição adicional: na verdade, há uma maneira ainda melhor de fornecer comparação E hash para muitas classes, incluindo aquele na questão - um
__key__
método, como mencionei em meu comentário à questão. Já que nunca tive a oportunidade de escrever o PEP para ele, você deve implementá-lo atualmente com um Mixin (& c) se desejar:É um caso muito comum para as comparações de uma instância com outras instâncias se resumirem à comparação de uma tupla para cada uma com alguns campos - e então, o hash deve ser implementado exatamente da mesma forma. O
__key__
método especial aborda essa necessidade diretamente.fonte
TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixin
quando tento isso em Python 3. Veja o código completo em gist.github.com/2696496functools.total_ordering
vez de construir o seu próprioComparableMixim
. Como sugerido na resposta de jmagnusson<
para implementar__eq__
em Python 3 é uma ideia muito ruim, por causa deTypeError: unorderable types
.Para simplificar este caso, há um decorador de classe no Python 2.7 + / 3.2 +, functools.total_ordering , que pode ser usado para implementar o que Alex sugere. Exemplo dos documentos:
fonte
total_ordering
não implementa__ne__
, por isso esteja atento!__ne__
. mas isso é porque__ne__
tem implementação padrão que delega para__eq__
. Portanto, não há nada a ser observado aqui.Isso é coberto pelo PEP 207 - Comparações ricas
Além disso,
__cmp__
desaparece no python 3.0. (Observe que não está presente em http://docs.python.org/3.0/reference/datamodel.html, mas ESTÁ em http://docs.python.org/2.7/reference/datamodel.html )fonte
(Editado em 17/06/17 para levar os comentários em consideração.)
Eu tentei a resposta mixin comparável acima. Tive problemas com "Nenhum". Aqui está uma versão modificada que lida com comparações de igualdade com "Nenhum". (Não vi razão para me preocupar com as comparações de desigualdade com Nenhum por falta de semântica):
fonte
self
poderia ser o solteirãoNone
deNoneType
e, ao mesmo tempo implementar o seuComparableMixin
? E, de fato, esta receita é ruim para Python 3.self
vai não serNone
, de modo a que ramo pode ir completamente. Não usetype(other) == type(None)
; simplesmente useother is None
. Em vez de especial-invólucroNone
, de teste, se o outro tipo é um exemplo do tipo deself
, e devolver oNotImplemented
Singleton se não:if not isinstance(other, type(self)): return NotImplemented
. Faça isso para todos os métodos. Python, então, pode dar ao outro operando uma chance de fornecer uma resposta.Inspirado pelas respostas
ComparableMixin
e pelasKeyedMixin
respostas de Alex Martelli , eu vim com o seguinte mixin. Ele permite que você implemente um único_compare_to()
método, que usa comparações baseadas em chave semelhantes aKeyedMixin
, mas permite que sua classe escolha a chave de comparação mais eficiente com base no tipo deother
. (Observe que este mixin não ajuda muito para objetos que podem ser testados quanto à igualdade, mas não à ordem).fonte