Por que o seguinte se comporta inesperadamente em Python?
>>> a = 256
>>> b = 256
>>> a is b
True # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False # What happened here? Why is this False?
>>> 257 is 257
True # Yet the literal numbers compare properly
Estou usando o Python 2.5.2. Tentando algumas versões diferentes do Python, parece que o Python 2.3.3 mostra o comportamento acima entre 99 e 100.
Com base no exposto, posso sugerir que o Python seja implementado internamente, de modo que números inteiros "pequenos" sejam armazenados de uma maneira diferente dos números inteiros maiores e o is
operador possa perceber a diferença. Por que a abstração com vazamento? Qual é a melhor maneira de comparar dois objetos arbitrários para ver se são iguais quando não sei antecipadamente se são números ou não?
Respostas:
Dê uma olhada neste:
Aqui está o que eu encontrei na documentação do Python 2, "Objetos inteiros simples" (é o mesmo para o Python 3 ):
fonte
Em resumo - deixe-me enfatizar: não use
is
para comparar números inteiros.Não é um comportamento sobre o qual você deve ter expectativas.
Em vez disso, use
==
e!=
compare para igualdade e desigualdade, respectivamente. Por exemplo:Explicação
Para saber isso, você precisa saber o seguinte.
Primeiro, o que
is
faz? É um operador de comparação. A partir da documentação :E assim o seguinte é equivalente.
A partir da documentação :
Observe que o fato de o ID de um objeto no CPython (a implementação de referência do Python) ser o local na memória é um detalhe da implementação. Outras implementações do Python (como Jython ou IronPython) podem facilmente ter uma implementação diferente para
id
.Então, para que serve o caso de uso
is
? O PEP8 descreve :A questão
Você faz e declara a seguinte pergunta (com código):
É não um resultado esperado. Por que isso é esperado? Isso significa apenas que os números inteiros avaliados em
256
referenciados por ambosa
eb
são a mesma instância do número inteiro. Os números inteiros são imutáveis em Python, portanto, eles não podem mudar. Isso não deve ter impacto em nenhum código. Não deve ser esperado. É apenas um detalhe de implementação.Mas talvez devêssemos estar contentes por não haver uma nova instância separada na memória toda vez que declaramos um valor igual a 256.
Parece que agora temos duas instâncias separadas de números inteiros com o valor de
257
na memória. Como os números inteiros são imutáveis, isso desperdiça memória. Vamos torcer para não desperdiçarmos muito. Provavelmente não somos. Mas esse comportamento não é garantido.Bem, parece que sua implementação específica do Python está tentando ser inteligente e não criando números inteiros com valor redundante na memória, a menos que seja necessário. Você parece indicar que está usando a implementação referente do Python, que é o CPython. Bom para CPython.
Pode ser ainda melhor se o CPython puder fazer isso globalmente, se puder fazê-lo de forma barata (como haveria um custo na pesquisa), talvez outra implementação o faça.
Mas quanto ao impacto no código, você não deve se importar se um número inteiro é uma instância específica de um número inteiro. Você deve se importar apenas com o valor dessa instância e usaria os operadores de comparação normais para isso, ou seja
==
.O que
is
fazis
verifica se osid
dois objetos são iguais. No CPython, esseid
é o local na memória, mas pode ser outro número de identificação exclusiva em outra implementação. Para reafirmar isso com o código:é o mesmo que
Por que queremos usar
is
então?Pode ser uma verificação muito rápida em relação a dizer, verificando se duas seqüências muito longas são iguais em valor. Mas, como se aplica à singularidade do objeto, temos casos de uso limitados para ele. De fato, queremos principalmente usá-lo para verificar
None
, que é um singleton (uma única instância existente em um local na memória). Poderíamos criar outros singletons se houver potencial para confundi-los, o que poderíamos verificaris
, mas estes são relativamente raros. Aqui está um exemplo (funcionará em Python 2 e 3), por exemploQue imprime:
E assim vemos, com
is
e com um sentinela, somos capazes de diferenciar entre quandobar
é chamado sem argumentos e quando é chamado comNone
. Estes são os principais casos de usois
- não o use para testar a igualdade de números inteiros, seqüências de caracteres, tuplas ou outras coisas como essas.fonte
is
- não use-o para testar a igualdade de números inteiros, seqüências de caracteres, tuplas ou outras coisas como essas." No entanto, estou tentando integrar uma máquina de estado simples em minha classe e, como os estados são valores opacos cuja única propriedade observável é a de ser idêntica ou diferente, parece natural que eles sejam comparáveisis
. Eu pretendo usar seqüências de caracteres internas como estados. Eu teria preferido números inteiros simples, mas, infelizmente, o Python não pode internar números inteiros (0 is 0
é um detalhe de implementação).Depende se você está olhando para ver se duas coisas são iguais ou o mesmo objeto.
is
verifica se eles são o mesmo objeto, não apenas iguais. As pequenas entradas provavelmente estão apontando para o mesmo local de memória para eficiência de espaçoVocê deve usar
==
para comparar a igualdade de objetos arbitrários. Você pode especificar o comportamento com os__eq__
e__ne__
atributos.fonte
Estou atrasado, mas você quer alguma fonte com a sua resposta? Vou tentar escrever isso de maneira introdutória para que mais pessoas possam acompanhar.
Uma coisa boa do CPython é que você pode realmente ver a fonte disso. Vou usar links para a versão 3.5 , mas encontrando o 2.x correspondente é trivial.
No CPython, a função C-API que lida com a criação de um novo
int
objeto éPyLong_FromLong(long v)
. A descrição para esta função é:(Meus itálicos)
Não sei sobre você, mas eu vejo isso e penso: vamos encontrar essa matriz!
Se você não mexeu no código C implementando o CPython, deveria ; tudo é bem organizado e legível. Para o nosso caso, precisamos procurar no
Objects
subdiretório da árvore de diretórios do código fonte principal .PyLong_FromLong
lida comlong
objetos, portanto não deve ser difícil deduzir que precisamos espiar por dentrolongobject.c
. Depois de olhar para dentro, você pode pensar que as coisas são caóticas; elas são, mas não temam, a função que procuramos é relaxar na linha 230, esperando que a verifiquemos. Como é uma função pequena, o corpo principal (excluindo as declarações) é facilmente colado aqui:Agora, não somos C -master-code-haxxorz, mas também não somos burros, podemos ver isso
CHECK_SMALL_INT(ival);
nos observando sedutoramente; podemos entender que isso tem algo a ver com isso. Vamos conferir:Portanto, é uma macro que chama função
get_small_int
se o valorival
satisfizer a condição:Então, o que são
NSMALLNEGINTS
eNSMALLPOSINTS
? Macros! Aqui estão elas :Então, nossa condição é
if (-5 <= ival && ival < 257)
chamadaget_small_int
.A seguir, veremos
get_small_int
toda a sua glória (bem, apenas veremos seu corpo porque é aí que estão as coisas interessantes):Ok, declare a
PyObject
, afirme que a condição anterior mantém e execute a atribuição:small_ints
parece muito com a matriz que estamos procurando, e é! Poderíamos ter lido a maldita documentação e saberíamos o tempo todo! :Então sim, esse é o nosso cara. Quando você deseja criar um novo
int
no intervalo,[NSMALLNEGINTS, NSMALLPOSINTS)
basta obter uma referência a um objeto já existente que foi pré-alocado.Como a referência se refere ao mesmo objeto, emitindo
id()
diretamente ou verificando a identidade comis
ele retornará exatamente a mesma coisa.Mas, quando eles são alocados?
Durante a inicialização em
_PyLong_Init
Python, com prazer, entrará em um loop for, faça isso por você:Confira a fonte para ler o corpo do loop!
Espero que a minha explicação te fez C coisas claramente agora (trocadilho obviamente intented).
Mas
257 is 257
? E aí?Isso é realmente mais fácil de explicar, e eu já tentei fazê-lo ; isso se deve ao fato de o Python executar esta declaração interativa como um único bloco:
Durante a conclusão desta declaração, o CPython verá que você tem dois literais correspondentes e usará a mesma
PyLongObject
representação257
. Você pode ver isso se fizer a compilação e examinar seu conteúdo:Quando o CPython faz a operação, agora ele carrega exatamente o mesmo objeto:
Então
is
vai voltarTrue
.fonte
Como você pode verificar no arquivo de origem intobject.c , o Python armazena em cache pequenos números inteiros por eficiência. Toda vez que você cria uma referência a um número inteiro pequeno, está se referindo ao número inteiro pequeno em cache, e não a um novo objeto. 257 não é um número inteiro pequeno, portanto é calculado como um objeto diferente.
É melhor usar
==
para esse fim.fonte
Eu acho que suas hipóteses estão corretas. Experiência com
id
(identidade do objeto):Parece que os números
<= 255
são tratados como literais e qualquer coisa acima é tratada de maneira diferente!fonte
Para objetos de valor imutável, como ints, strings ou datetime, a identidade do objeto não é especialmente útil. É melhor pensar em igualdade. A identidade é essencialmente um detalhe de implementação para objetos de valor - uma vez que são imutáveis, não há diferença efetiva entre ter várias referências ao mesmo objeto ou vários objetos.
fonte
Há outra questão que não é apontada em nenhuma das respostas existentes. É permitido ao Python mesclar dois valores imutáveis, e valores int pequenos e pré-criados não são a única maneira de isso acontecer. Nunca é garantido que uma implementação Python faça isso, mas todas elas fazem isso por mais do que apenas pequenas ints.
Por um lado, existem alguns outros valores pré-criados, como o vazio
tuple
,str
ebytes
, e algumas seqüências curtas (em CPython 3.6, é os 256 single-caráter cordas Latin-1). Por exemplo:Mas também, mesmo valores não pré-criados podem ser idênticos. Considere estes exemplos:
E isso não se limita aos
int
valores:Obviamente, o CPython não vem com um
float
valor pré-criado para42.23e100
. Então, o que está acontecendo aqui?O compilador CPython irá mesclar valores constantes de alguns tipos conhecida-imutáveis, como
int
,float
,str
,bytes
, na mesma unidade de compilação. Para um módulo, o módulo inteiro é uma unidade de compilação, mas no intérprete interativo, cada instrução é uma unidade de compilação separada. Comoc
ed
são definidos em instruções separadas, seus valores não são mesclados. Comoe
ef
são definidos na mesma instrução, seus valores são mesclados.Você pode ver o que está acontecendo desmontando o bytecode. Tente definir uma função que faça
e, f = 128, 128
e depois invoquedis.dis
-a, e você verá que existe um único valor constante(128, 128)
Você pode perceber que o compilador armazenou
128
como uma constante, mesmo que não seja realmente usado pelo bytecode, o que lhe dá uma idéia de quão pouca otimização o compilador do CPython faz. O que significa que as tuplas (não vazias) na verdade não acabam mescladas:Coloque isso em uma função,
dis
e observe asco_consts
- existem a1
e a2
, duas(1, 2)
tuplas que compartilham o mesmo1
e2
mas não são idênticas, e uma((1, 2), (1, 2))
tupla que possui as duas iguais iguais distintas.Há mais uma otimização que o CPython faz: cadeia de caracteres interna. Diferente da dobragem constante do compilador, isso não se restringe aos literais do código-fonte:
Por outro lado, é limitado ao
str
tipo e às cadeias de armazenamento interno do tipo "ascii compact", "compact" ou "legacy ready" e, em muitos casos, apenas o "ascii compact" será internado.De qualquer forma, as regras para quais valores devem ser, podem ser ou não podem ser diferentes variam de implementação para implementação e entre versões da mesma implementação e talvez até entre execuções do mesmo código na mesma cópia da mesma implementação. .
Pode valer a pena aprender as regras de um Python específico para se divertir. Mas não vale a pena confiar neles no seu código. A única regra segura é:
x is y
, usex == y
)x is not y
, usex != y
)Ou, em outras palavras, use apenas
is
para testar os singletons documentados (comoNone
) ou que são criados apenas em um local no código (como o_sentinel = object()
idioma).fonte
x is y
para comparar, usex == y
. Da mesma forma não usex is not y
, usex != y
a=257; b=257
em uma única linhaa is b
Trueis
é o operador de igualdade de identidade (funcionando comoid(a) == id(b)
); é que dois números iguais não são necessariamente o mesmo objeto. Por motivos de desempenho, alguns inteiros pequenos são memorizados, de forma que tendem a ser os mesmos (isso pode ser feito porque são imutáveis).O
===
operador do PHP , por outro lado, é descrito como verificação de igualdade e tipo:x == y and type(x) == type(y)
conforme comentário de Paulo Freitas. Isso será suficiente para números comuns, mas diferirá dasis
classes que definem de__eq__
maneira absurda:Aparentemente, o PHP permite o mesmo para as classes "internas" (o que eu entendo como implementadas no nível C, não no PHP). Um uso um pouco menos absurdo pode ser um objeto de timer, que tem um valor diferente toda vez que é usado como um número. Exatamente por que você gostaria de emular o Visual Basic, em
Now
vez de mostrar que é uma avaliação comtime.time()
eu não sei.Greg Hewgill (OP) fez um comentário esclarecedor: "Meu objetivo é comparar a identidade do objeto, em vez da igualdade de valor. Exceto pelos números, onde desejo tratar a identidade do objeto da mesma forma que a igualdade de valor".
Isso teria ainda outra resposta, pois precisamos categorizar as coisas como números ou não, para selecionar se comparamos com
==
ouis
. O CPython define o protocolo numérico , incluindo PyNumber_Check, mas isso não é acessível no próprio Python.Poderíamos tentar usar
isinstance
com todos os tipos de números que conhecemos, mas isso seria inevitavelmente incompleto. O módulo types contém uma lista StringTypes, mas não NumberTypes. Desde o Python 2.6, as classes numéricas incorporadas têm uma classe basenumbers.Number
, mas têm o mesmo problema:A propósito, o NumPy produzirá instâncias separadas de números baixos.
Na verdade, não sei uma resposta para essa variante da pergunta. Suponho que alguém possa teoricamente usar ctypes para chamar
PyNumber_Check
, mas mesmo essa função foi debatida e certamente não é portátil. Teremos que ser menos específicos sobre o que testamos por enquanto.No final, esse problema decorre do Python não ter originalmente uma árvore de tipos com predicados como Scheme's
number?
ou a classe de tipos Num de Haskell .is
verifica a identidade do objeto, não valoriza a igualdade. O PHP também tem uma história colorida, onde===
aparentemente se comportais
apenas em objetos no PHP5, mas não no PHP4 . Essas são as dores crescentes de se mover entre idiomas (incluindo versões de um).fonte
Isso também acontece com as strings:
Agora tudo parece bem.
Isso também é esperado.
Agora isso é inesperado.
fonte
'xx'
é como o esperado, como é'xxx'
, mas'x x'
não é.xx
em qualquer lugar da sua sessão do Python, essa sequência já estará internada; e pode haver uma heurística que faça isso se parecer com um nome. Como nos números, isso pode ser feito porque são imutáveis. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interningNovidades no Python 3.8: Alterações no comportamento do Python :
fonte