Blog

is vs == em Python: identidade e igualdade

== 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, is compara identidade.

  • Use is só para None, True, False e 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.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.