Ao escrever classes personalizadas, geralmente é importante permitir equivalência por meio dos operadores ==
e !=
. No Python, isso é possível implementando os métodos especiais __eq__
e __ne__
, respectivamente. A maneira mais fácil de encontrar isso é o seguinte método:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
Você conhece meios mais elegantes de fazer isso? Você conhece alguma desvantagem específica em usar o método acima para comparar __dict__
s?
Nota : Um pouco de esclarecimento - quando __eq__
e __ne__
não definido, você encontrará este comportamento:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Ou seja, a == b
avalia False
como realmente executa a is b
, um teste de identidade (ou seja, "É a
o mesmo objeto que b
?").
Quando __eq__
e __ne__
estiver definido, você encontrará esse comportamento (que é o que procuramos):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
python
equality
equivalence
gotgenes
fonte
fonte
is
operador para distinguir a identidade do objeto da comparação de valores.Respostas:
Considere este problema simples:
Portanto, o Python, por padrão, usa os identificadores de objeto para operações de comparação:
A substituição da
__eq__
função parece resolver o problema:No Python 2 , lembre-se sempre de substituir a
__ne__
função também, conforme a documentação declara:No Python 3 , isso não é mais necessário, pois a documentação declara:
Mas isso não resolve todos os nossos problemas. Vamos adicionar uma subclasse:
Nota: Python 2 possui dois tipos de classes:
de estilo clássico (ou de estilo antigo ) classes, que não herdam
object
e que são declarados comoclass A:
,class A():
ouclass A(B):
ondeB
é uma classe de estilo clássico;classes de novo estilo , que herdam
object
e são declaradas comoclass A(object)
ouclass A(B):
ondeB
é uma classe de novo estilo. O Python 3 possui apenas classes de novo estilo que são declaradas comoclass A:
,class A(object):
ouclass A(B):
.Para classes de estilo clássico, uma operação de comparação sempre chama o método do primeiro operando, enquanto para classes de novo estilo, sempre chama o método do operando da subclasse, independentemente da ordem dos operandos .
Então, aqui, se
Number
é uma classe de estilo clássico:n1 == n3
chamadasn1.__eq__
;n3 == n1
chamadasn3.__eq__
;n1 != n3
chamadasn1.__ne__
;n3 != n1
chamadasn3.__ne__
.E se
Number
é uma classe de novo estilo:n1 == n3
en3 == n1
ligarn3.__eq__
;n1 != n3
en3 != n1
liguen3.__ne__
.Para corrigir o problema de não comutatividade dos operadores
==
e!=
para as classes de estilo clássico do Python 2, os métodos__eq__
e__ne__
devem retornar oNotImplemented
valor quando um tipo de operando não for suportado. A documentação define oNotImplemented
valor como:Nesse caso, o operador delega a operação de comparação para o método refletido do outro operando. A documentação define métodos refletidos como:
O resultado fica assim:
Retornar o
NotImplemented
valor em vez deFalse
é a coisa certa a fazer, mesmo para as classes de novo estilo, se a comutatividade dos operadores==
e!=
for desejada quando os operandos forem de tipos não relacionados (sem herança).Já estamos lá? Nem tanto. Quantos números únicos temos?
Os conjuntos usam os hashes dos objetos e, por padrão, o Python retorna o hash do identificador do objeto. Vamos tentar substituí-lo:
O resultado final é semelhante a este (adicionei algumas afirmações no final para validação):
fonte
hash(tuple(sorted(self.__dict__.items())))
não funcionará se houver objetos não hashable entre os valores deself.__dict__
(ou seja, se algum dos atributos do objeto estiver definido como, digamos, alist
).__ne__
usando em==
vez de__eq__
.__ne__
: "Por padrão,__ne__()
delega__eq__()
e inverte o resultado, a menos que sejaNotImplemented
". 2. Se alguém ainda quer implementar__ne__
, uma implementação mais genérico (aquele usado pelo Python 3 eu acho) é:x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. O dado__eq__
e__ne__
implementações são abaixo do ideal:if isinstance(other, type(self)):
dá 22__eq__
e 10__ne__
chamadas, enquantoif isinstance(self, type(other)):
daria 16__eq__
e 6__ne__
chamadas.Você precisa ter cuidado com a herança:
Verifique os tipos com mais rigor, assim:
Além disso, sua abordagem funcionará bem, é para isso que existem métodos especiais.
fonte
NotImplemented
como você sugere sempre causarásuperclass.__eq__(subclass)
, qual é o comportamento desejado.if other is self
. Isso evita a comparação mais longa do dicionário e pode ser uma grande economia quando os objetos são usados como chaves do dicionário.__hash__()
O jeito que você descreve é o que eu sempre fiz. Como é totalmente genérico, você sempre pode dividir essa funcionalidade em uma classe mixin e herdá-la nas classes em que deseja essa funcionalidade.
fonte
other
é de uma subclasse deself.__class__
.__dict__
comparação é o que acontece se você tiver um atributo que não deseja considerar em sua definição de igualdade (por exemplo, um ID de objeto exclusivo ou metadados como um carimbo criado no tempo).Não é uma resposta direta, mas parecia relevante o suficiente para ser abordada, pois economiza um pouco de tédio detalhado na ocasião. Corte direto dos documentos ...
functools.total_ordering (cls)
Dada uma classe que define um ou mais métodos avançados de pedido de comparação, esse decorador de classes fornece o restante. Isso simplifica o esforço envolvido na especificação de todas as operações possíveis de comparação rica:
A classe deve definir um dos
__lt__()
,__le__()
,__gt__()
, ou__ge__()
. Além disso, a classe deve fornecer um__eq__()
método.Novo na versão 2.7
fonte
Você não precisa substituir os dois
__eq__
e__ne__
pode substituir apenas,__cmp__
mas isso implicará no resultado de ==,! ==, <,> e assim por diante.is
testes para identidade de objeto. Isso significa que ais
b ocorreráTrue
quando aeb tiverem a referência ao mesmo objeto. No python, você sempre mantém uma referência a um objeto em uma variável e não no objeto real; portanto, essencialmente para a ser b, os objetos neles devem estar localizados no mesmo local da memória. Como e mais importante, por que você substitui esse comportamento?Edit: Eu não sabia que
__cmp__
foi removido do python 3, para evitá-lo.fonte
A partir desta resposta: https://stackoverflow.com/a/30676267/541136 Eu demonstrei que, embora seja correto definir
__ne__
em termos__eq__
- em vez devocê deveria usar:
fonte
Eu acho que os dois termos que você procura são igualdade (==) e identidade (é). Por exemplo:
fonte
O teste 'is' testará a identidade usando a função 'id ()' incorporada, que basicamente retorna o endereço de memória do objeto e, portanto, não pode ser sobrecarregado.
No entanto, no caso de testar a igualdade de uma classe, você provavelmente deseja ser um pouco mais rigoroso sobre seus testes e comparar apenas os atributos de dados em sua classe:
Este código compara apenas membros de dados não funcionais da sua classe, além de ignorar qualquer coisa particular que geralmente seja o que você deseja. No caso de Plain Old Python Objects, tenho uma classe base que implementa __init__, __str__, __repr__ e __eq__, para que meus objetos POPO não carreguem o fardo de toda essa lógica extra (e na maioria dos casos idêntica).
fonte
__eq__
declaração for declarada emCommonEqualityMixin
(veja a outra resposta). Achei isso particularmente útil ao comparar instâncias de classes derivadas de Base em SQLAlchemy. Para não comparar_sa_instance_state
, mudeikey.startswith("__")):
parakey.startswith("_")):
. Eu também tinha algumas referências anteriores e a resposta de Algorias gerou uma recursão infinita. Então, nomeei todas as referências anteriores, começando com,'_'
para que elas também sejam ignoradas durante a comparação. NOTA: no Python 3.x, mudeiteritems()
paraitems()
.__dict__
uma instância não tem nada que comece com, a__
menos que tenha sido definida pelo usuário. Coisas como__class__
,__init__
etc. não estão nas instâncias__dict__
, mas na classe '__dict__
. OTOH, os atributos privados podem facilmente começar__
e provavelmente devem ser usados__eq__
. Você pode esclarecer o que exatamente estava tentando evitar ao ignorar__
atributos predefinidos?Em vez de usar subclasses / mixins, eu gosto de usar um decorador de classe genérico
Uso:
fonte
Isso incorpora os comentários na resposta de Algorias e compara objetos por um único atributo, porque eu não ligo para o ditado inteiro.
hasattr(other, "id")
deve ser verdade, mas eu sei que é porque eu o defini no construtor.fonte