== pergunta se dois objetos têm o mesmo valor. is pergunta se eles são o mesmo objeto na memória.
Eles concordam com frequência suficiente para que usar o errado quase sempre funcione — e é justamente isso que torna o bug difícil quando ele aparece.
A diferença
Duas listas com o mesmo conteúdo são iguais e não são idênticas.
# == compares values, is compares identity
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b, a is b)
c = a
print(a == c, a is c)
Ele imprime:
True False
True True
Atribua uma à outra e os dois viram verdadeiros, porque existe uma lista só.
Use is para None
None é um singleton. Existe exatamente um, então identidade é a pergunta certa.
# for None, always use is
value = None
print(value is None, value == None)
Ele imprime:
True True
== None normalmente funciona e continua errado, pelo motivo da próxima seção.
Igualdade pode ser redefinida
Uma classe pode fazer == significar qualquer coisa. is não pode ser sobrescrito.
# == can be redefined, is cannot
class Always:
def __eq__(self, other):
return True
x = Always()
print(x == 'anything', x == 42)
print(x is Always())
Ele imprime:
True True
False
Se esse objeto fosse comparado com == None, a verificação passaria e o seu tratamento de None rodaria em cima de um objeto de verdade.
Por que números pequenos parecem idênticos
O CPython guarda inteiros pequenos em cache, e constantes dentro de um mesmo code object são compartilhadas. Isso faz is devolver True onde você esperaria False.
# small integers are cached by CPython
m, n = 256, 256
print('256:', m is n)
p = 1000
q = 1000
print('1000 literals:', p is q) # same code object, so the constant is shared
r = int('1000')
print('1000 at runtime:', p is r, p == r)
Ele imprime:
256: True
1000 literals: True
1000 at runtime: False True
A última linha é a honesta. Dois 1000 construídos do mesmo jeito compartilham um objeto aqui, mas um 1000 construído em tempo de execução não compartilha. Nada disso é garantido pela linguagem, então nunca escreva comparações que dependam disso.
Strings fazem a mesma coisa
Strings literais são internalizadas. Strings construídas em tempo de execução não são.
# the same happens with short strings
s1 = 'hello'
s2 = 'hello'
print('literal:', s1 is s2)
s3 = ''.join(['hel', 'lo'])
print('built at runtime:', s1 is s3, s1 == s3)
Ele imprime:
literal: True
built at runtime: False True
É por isso que if name is "admin" passa nos testes e falha em produção, onde o nome chega por uma requisição.
Identidade é o que te diz se algo foi alterado
Esse é o lado útil do is. Ele mostra se uma operação criou um objeto novo ou mudou o que você já tinha.
# identity is what actually changed
first = [1, 2]
second = first
first = first + [3] # rebinds, new object
print(first, second, first is second)
third = [1, 2]
fourth = third
third += [3] # mutates in place
print(third, fourth, third is fourth)
Ele imprime:
[1, 2, 3] [1, 2] False
[1, 2, 3] [1, 2, 3] True
O + construiu uma lista nova e reapontou o nome, então o segundo nome ainda vê o valor antigo. O += alterou a lista no lugar, então os dois nomes veem a mudança. Mesmo resultado na tela, comportamento diferente para qualquer outro código que tenha uma referência.
O que lembrar
-
==compara valores,iscompara identidade. -
Use
issó paraNone,True,Falsee sentinelas. -
O cache de inteiros pequenos e de strings literais é um detalhe do CPython. Não construa lógica em cima disso.
-
isé a ferramenta para verificar se algo foi copiado ou alterado.
Linters marcam is contra um literal exatamente por isso. Se o seu não marca, ligue essa verificação.