Considere este problema simples:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Portanto, o Python, por padrão, usa os identificadores de objeto para operações de comparação:
id(n1) # 140400634555856
id(n2) # 140400634555920
A substituição da __eq__função parece resolver o problema:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
No Python 2 , lembre-se sempre de substituir a __ne__função também, conforme a documentação declara:
Não há relacionamentos implícitos entre os operadores de comparação. A verdade de x==ynão implica que isso x!=yseja falso. Assim, ao definir __eq__(), deve-se definir também __ne__()para que os operadores se comportem conforme o esperado.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
No Python 3 , isso não é mais necessário, pois a documentação declara:
Por padrão, __ne__()delega __eq__()e inverte o resultado, a menos que seja NotImplemented. Não há outras relações implícitas entre os operadores de comparação, por exemplo, a verdade de (x<y or x==y)não implica x<=y.
Mas isso não resolve todos os nossos problemas. Vamos adicionar uma subclasse:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Nota: Python 2 possui dois tipos de classes:
de estilo clássico (ou de estilo antigo ) classes, que não herdamobjecte que são declarados comoclass A:,class A():ouclass A(B):ondeBé uma classe de estilo clássico;
classes de novo estilo , que herdamobjecte 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 == n3chamadas n1.__eq__;
n3 == n1chamadas n3.__eq__;
n1 != n3chamadas n1.__ne__;
n3 != n1chamadas n3.__ne__.
E se Numberé uma classe de novo estilo:
- ambos
n1 == n3e n3 == n1ligar n3.__eq__;
- ambos
n1 != n3e n3 != n1ligue n3.__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 o NotImplementedvalor quando um tipo de operando não for suportado. A documentação define o NotImplementedvalor como:
Métodos numéricos e métodos de comparação avançados podem retornar esse valor se eles não implementarem a operação para os operandos fornecidos. (O intérprete tentará a operação refletida ou algum outro fallback, dependendo do operador.) Seu valor verdadeiro é verdadeiro.
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:
Não há versões de argumento trocado desses métodos (a serem usadas quando o argumento esquerdo não suporta a operação, mas o argumento correto); sim, __lt__()e __gt__()são reflexo um do outro, __le__()e __ge__()são reflexo um do outro, e
__eq__()e __ne__()são a sua própria reflexão.
O resultado fica assim:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Retornar o NotImplementedvalor em vez de Falseé 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?
len(set([n1, n2, n3])) # 3 -- oops
Os conjuntos usam os hashes dos objetos e, por padrão, o Python retorna o hash do identificador do objeto. Vamos tentar substituí-lo:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
O resultado final é semelhante a este (adicionei algumas afirmações no final para validação):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
isoperador para distinguir a identidade do objeto da comparação de valores.