Python! = Operação vs "não é"

250

Em um comentário sobre essa pergunta , vi uma declaração que recomendava o uso de

result is not None

vs

result != None

Eu queria saber qual é a diferença, e por que um pode ser recomendado em detrimento do outro?

viksit
fonte
1
Hmm. Embora a resposta para ambas as perguntas seja o mesmo conceito, acho que as respostas positivas e detalhadas aqui contribuem independentemente para o conceito de teste de identidade e igualdade.
viksit

Respostas:

301

==é um teste de igualdade . Ele verifica se o lado direito e o lado esquerdo são iguais objectos (de acordo com as suas __eq__ou __cmp__métodos.)

isé um teste de identidade . Ele verifica se o lado direito e o lado esquerdo são o mesmo objeto. Nenhuma chamada de método é feita, os objetos não podem influenciar a isoperação.

Você usa is(e is not) para singletons, como None, onde você não se importa com objetos que possam querer fingir estar Noneou onde deseja proteger contra objetos quebrados ao serem comparados None.

Thomas Wouters
fonte
3
Obrigado pela resposta - você poderia elaborar situações em que um objeto pode quebrar, sendo comparado a Nenhum?
viksit
3
@viksit. Nonetem poucos métodos e quase nenhum atributo. Se o seu __eq__teste esperava um método ou atributo, ele pode quebrar. def __eq__( self, other ): return self.size == other.size. Por exemplo, irá quebrar se otheracontecer None.
S.Lott
36
Minha maneira favorita de entender isso é: o Python isé como o Java ==. O Python ==é como o Java .equals(). Claro que isso só ajuda se você conhece Java.
MatrixFrog
4
@MatrixFrog: Em PHP ou JavaScript, diríamos que isé como ===(muito igual) e, inversamente, is noté como !==(não é exatamente igual).
Orwellophile
3
É is notum único operador ou está apenas negando o resultado de isgostar internamente not foo is bar?
Asad Moosvi
150

Primeiro, deixe-me passar por alguns termos. Se você apenas deseja que sua pergunta seja respondida, role para baixo até "Respondendo a sua pergunta".

Definições

Identidade do objeto : ao criar um objeto, você pode atribuí-lo a uma variável. Você também pode atribuí-lo a outra variável. E outro.

>>> button = Button()
>>> cancel = button
>>> close = button
>>> dismiss = button
>>> print(cancel is close)
True

Neste caso, cancel, close, e dismisstodos se referem ao mesmo objeto na memória. Você criou apenas um Buttonobjeto e todas as três variáveis ​​se referem a esse único objeto. Dizemos que cancel, closee dismisstodos se referem a idênticas objetos; isto é, eles se referem a um único objeto.

Igualdade de objetos : quando você compara dois objetos, geralmente não se importa que se refira exatamente ao mesmo objeto na memória. Com a igualdade de objetos, você pode definir suas próprias regras para comparar dois objetos. Quando você escreve if a == b:, você está dizendo essencialmente if a.__eq__(b):. Isso permite definir um __eq__método ema para que você possa usar sua própria lógica de comparação.

Justificativa para comparações de igualdade

Justificativa: Dois objetos têm exatamente os mesmos dados, mas não são idênticos. (Eles não são o mesmo objeto na memória.) Exemplo: Strings

>>> greeting = "It's a beautiful day in the neighbourhood."
>>> a = unicode(greeting)
>>> b = unicode(greeting)
>>> a is b
False
>>> a == b
True

Nota: Eu uso cadeias unicode aqui porque o Python é inteligente o suficiente para reutilizar cadeias regulares sem criar novas na memória.

Aqui, eu tenho duas cadeias unicode, ae b. Eles têm exatamente o mesmo conteúdo, mas não são o mesmo objeto na memória. No entanto, quando os comparamos, queremos que eles comparem iguais. O que está acontecendo aqui é que o objeto unicode implementou o __eq__método.

class unicode(object):
    # ...

    def __eq__(self, other):
        if len(self) != len(other):
            return False

        for i, j in zip(self, other):
            if i != j:
                return False

        return True

Nota: __eq__on unicodeé definitivamente implementado com mais eficiência do que isso.

Justificativa: Dois objetos têm dados diferentes, mas são considerados o mesmo objeto se alguns dados importantes forem os mesmos. Exemplo: a maioria dos tipos de dados do modelo

>>> import datetime
>>> a = Monitor()
>>> a.make = "Dell"
>>> a.model = "E770s"
>>> a.owner = "Bob Jones"
>>> a.warranty_expiration = datetime.date(2030, 12, 31)
>>> b = Monitor()
>>> b.make = "Dell"
>>> b.model = "E770s"
>>> b.owner = "Sam Johnson"
>>> b.warranty_expiration = datetime.date(2005, 8, 22)
>>> a is b
False
>>> a == b
True

Aqui, tenho dois monitores Dell ae b. Eles têm a mesma marca e modelo. No entanto, eles não têm os mesmos dados nem o mesmo objeto na memória. No entanto, quando os comparamos, queremos que eles comparem iguais. O que está acontecendo aqui é que o objeto Monitor implementou o __eq__método.

class Monitor(object):
    # ...

    def __eq__(self, other):
        return self.make == other.make and self.model == other.model

Respondendo sua pergunta

Ao comparar com None, use sempreis not . Nenhum é um singleton em Python - existe apenas uma instância na memória.

Comparando a identidade , isso pode ser realizado muito rapidamente. O Python verifica se o objeto ao qual você está se referindo tem o mesmo endereço de memória que o objeto None global - uma comparação muito, muito rápida de dois números.

Ao comparar a igualdade , o Python precisa verificar se o seu objeto possui um __eq__método. Caso contrário, examina cada superclasse procurando um __eq__método. Se encontrar, Python o chama. Isso é especialmente ruim se o __eq__método for lento e não retornar imediatamente quando perceber que o outro objeto estáNone .

Você não implementou __eq__? Então o Python provavelmente encontrará o __eq__método objecte o utilizará - o que apenas verifica a identidade do objeto de qualquer maneira.

Ao comparar a maioria das outras coisas em Python, você estará usando !=.

Wesley
fonte
42

Considere o seguinte:

class Bad(object):
    def __eq__(self, other):
        return True

c = Bad()
c is None # False, equivalent to id(c) == id(None)
c == None # True, equivalent to c.__eq__(None)
Alok Singhal
fonte
1
Este é um exemplo muito útil e simples. Obrigado.
msarafzadeh 01/02/19
18

Noneé um singleton, portanto, a comparação de identidade sempre funcionará, enquanto um objeto pode falsificar a comparação de igualdade via .__eq__().

Ignacio Vazquez-Abrams
fonte
Ah interessante! Em quais situações alguém pode querer fingir a comparação de igualdade entre os dois? Suponho que isso tenha implicações de segurança de alguma forma.
viksit
1
Não se trata de fingir igualdade, é de implementar igualdade. Há muitas razões para querer definir como um objeto se compara a outro.
Thomas Wouters
1
Eu diria que são mais implicações de confusão do que implicações de segurança.
Greg Hewgill
2
Não me deparei com um motivo para falsificar a igualdade None, mas um comportamento incorreto Nonepode ocorrer como um efeito colateral da implementação da igualdade contra outros tipos. Não são tantas implicações de segurança, mas apenas implicações de correção.
Ignacio Vazquez-Abrams
Ah, entendo. Obrigado pelo esclarecimento.
viksit
10
>>> () é ()
Verdade
>>> 1 é 1
Verdade
>>> (1,) == (1,)
Verdade
>>> (1,) é (1,)
Falso
>>> a = (1,)
>>> b = a
>>> a é b
Verdade

Alguns objetos são singletons e, portanto, iscom eles é equivalente a ==. A maioria não é.

efémero
fonte
4
A maioria deles funciona apenas por detalhes de coincidência / implementação. ()e 1não são inerentemente singletons.
Mike Graham
1
Na implementação do CPython, os inteiros pequenos ( -NSMALLNEGINTS <= n <= NSMALLPOSINTS) e as tuplas vazias são singletons. Na verdade, não está documentado nem garantido, mas é improvável que mude.
ephemient
3
É como é implementado, mas não é significativo, útil ou educacional.
Mike Graham
1
E, em particular, o CPython não é a única implementação do Python. Confiar em comportamentos que podem variar nas implementações do Python geralmente parece ser uma Bad Idea ™ para mim.
Jan_