O operador "is" se comporta inesperadamente com números inteiros

509

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 isoperador 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?

Greg Hewgill
fonte
1
Dê uma olhada aqui > A implementação atual mantém uma matriz de objetos inteiros para todos os números inteiros entre -5 e 256, quando você cria um int nesse intervalo, na verdade você apenas recebe uma referência ao objeto existente.
user5319825
2
Este é um detalhe específico do CPython implementação e um comportamento indefinido, uso com precauções
ospider

Respostas:

393

Dê uma olhada neste:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

Aqui está o que eu encontrei na documentação do Python 2, "Objetos inteiros simples" (é o mesmo para o Python 3 ):

A implementação atual mantém uma matriz de objetos inteiros para todos os números inteiros entre -5 e 256. Quando você cria um int nesse intervalo, na verdade, apenas recebe uma referência ao objeto existente. Portanto, deve ser possível alterar o valor de 1. Suspeito que o comportamento do Python neste caso seja indefinido. :-)

Cybis
fonte
46
alguém sabe como esse intervalo (-5, 256) foi escolhido? eu não ficaria muito surpreso se fosse (0, 255) ou mesmo (-255, 255), mas um intervalo de 262 números começando em -5 parece surpreendentemente arbitrário.
Woodrow Barlow
6
@WoodrowBarlow: O -5 é apenas uma heurística para capturar espaços reservados negativos comuns, eu acho. 0..255 abrange matrizes de valores de byte único. É 256 isso é misterioso, mas acho que é para (des) montar números inteiros em / de bytes.
Davis Herring
3
Pelo que entendi, o intervalo foi escolhido analisando os valores comumente usados ​​em vários projetos (e em vários idiomas).
Tony Suffolk 66
9
De acordo com reddit.com/r/Python/comments/18leav/… , o intervalo costumava ser [-5.100]. Foi expandido para incluir toda a gama de valores de bytes - mais 256, porque é presumivelmente um número comum.
Mwfearnley 7/07
2
@Ashwani tente ler os comentários ao lado do seu comentário, publicados dois anos antes do seu, e você encontrará a resposta para sua pergunta.
jbg 16/05
116

O operador "is" do Python se comporta inesperadamente com números inteiros?

Em resumo - deixe-me enfatizar: não use ispara 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:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Explicação

Para saber isso, você precisa saber o seguinte.

Primeiro, o que isfaz? É um operador de comparação. A partir da documentação :

Os operadores ise is nottestam a identidade do objeto: x is yé verdadeira se e somente se xey são o mesmo objeto. x is not yproduz o valor inverso da verdade.

E assim o seguinte é equivalente.

>>> a is b
>>> id(a) == id(b)

A partir da documentação :

id Retorne a "identidade" de um objeto. Esse é um número inteiro (ou inteiro longo) que é garantido como único e constante para esse objeto durante sua vida útil. Dois objetos com vida útil sem sobreposição podem ter o mesmo id()valor.

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 paraid .

Então, para que serve o caso de uso is? O PEP8 descreve :

As comparações com singletons como Nonesempre devem ser feitas com isou is not, nunca com os operadores de igualdade.

A questão

Você faz e declara a seguinte pergunta (com código):

Por que o seguinte se comporta inesperadamente em Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

É não um resultado esperado. Por que isso é esperado? Isso significa apenas que os números inteiros avaliados em 256referenciados por ambos ae bsã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.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Parece que agora temos duas instâncias separadas de números inteiros com o valor de 257na 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.

>>> 257 is 257
True           # Yet the literal numbers compare properly

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 isfaz

isverifica se os iddois objetos são iguais. No CPython, esse idé 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:

>>> a is b

é o mesmo que

>>> id(a) == id(b)

Por que queremos usar isentã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 verificar is, mas estes são relativamente raros. Aqui está um exemplo (funcionará em Python 2 e 3), por exemplo

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Que imprime:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

E assim vemos, com ise com um sentinela, somos capazes de diferenciar entre quando baré chamado sem argumentos e quando é chamado com None. Estes são os principais casos de uso is- não o use para testar a igualdade de números inteiros, seqüências de caracteres, tuplas ou outras coisas como essas.

Aaron Hall
fonte
"Estes são os principais casos de uso 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áveis is. 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).
Alexey
@Alexey parece que você precisa de enums? stackoverflow.com/questions/37601644/...
Aaron Hall
Talvez, obrigado, não os conhecesse. Esta pode ser uma adição apropriada para você responder à IMO.
Alexey
Talvez usando um número de objetos mudos como a sentinela em sua resposta seria uma solução mais leve ...
Alexey
As enumerações do @Alexey estão na biblioteca padrão do Python 3, e isso provavelmente incentivaria seu código a ser um pouco mais significativo do que sentinelas vazias.
Aaron Hall
60

Depende se você está olhando para ver se duas coisas são iguais ou o mesmo objeto.

isverifica 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ço

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Você deve usar ==para comparar a igualdade de objetos arbitrários. Você pode especificar o comportamento com os __eq__e __ne__atributos.

JimB
fonte
Polegares para cima para realmente explicar como comparar objetos arbitrários, como o OP pediu !!
Joooeey 11/04
54

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 intobjeto é PyLong_FromLong(long v). A descrição para esta função é:

A implementação atual mantém uma matriz de objetos inteiros para todos os números inteiros entre -5 e 256. Quando você cria um int nesse intervalo, na verdade, apenas recebe uma referência ao objeto existente . Portanto, deve ser possível alterar o valor de 1. Suspeito que o comportamento do Python neste caso seja indefinido. :-)

(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 Objectssubdiretório da árvore de diretórios do código fonte principal .

PyLong_FromLonglida com longobjetos, portanto não deve ser difícil deduzir que precisamos espiar por dentro longobject.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:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

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:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Portanto, é uma macro que chama função get_small_intse o valor ivalsatisfizer a condição:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Então, o que são NSMALLNEGINTSe NSMALLPOSINTS? Macros! Aqui estão elas :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Então, nossa condição é if (-5 <= ival && ival < 257)chamada get_small_int.

A seguir, veremos get_small_inttoda a sua glória (bem, apenas veremos seu corpo porque é aí que estão as coisas interessantes):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Ok, declare a PyObject, afirme que a condição anterior mantém e execute a atribuição:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_intsparece muito com a matriz que estamos procurando, e é! Poderíamos ter lido a maldita documentação e saberíamos o tempo todo! :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Então sim, esse é o nosso cara. Quando você deseja criar um novo intno 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ê:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

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:

>>> 257 is 257

Durante a conclusão desta declaração, o CPython verá que você tem dois literais correspondentes e usará a mesma PyLongObjectrepresentação 257. Você pode ver isso se fizer a compilação e examinar seu conteúdo:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Quando o CPython faz a operação, agora ele carrega exatamente o mesmo objeto:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Então isvai voltar True.

Dimitris Fasarakis Hilliard
fonte
37

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.

Anjo
fonte
19

Eu acho que suas hipóteses estão corretas. Experiência com id(identidade do objeto):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Parece que os números <= 255são tratados como literais e qualquer coisa acima é tratada de maneira diferente!

Amit
fonte
1
Isso ocorre porque os objetos que representam valores de -5 a +256 são criados no momento da inicialização - e, portanto, todo uso desses valores é usado para o objeto pré-construído. Quase todas as referências a números inteiros fora desse intervalo criam um novo objeto interno cada vez que são referenciadas. Eu acho que o uso do termo literal é confuso - literalmente se refere a qualquer valor digitado em um pedaço de código - então todo número no código-fonte é literal.
Tony Suffolk 66
13

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.

babbageclunk
fonte
12

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, stre bytes, e algumas seqüências curtas (em CPython 3.6, é os 256 single-caráter cordas Latin-1). Por exemplo:

>>> a = ()
>>> b = ()
>>> a is b
True

Mas também, mesmo valores não pré-criados podem ser idênticos. Considere estes exemplos:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

E isso não se limita aos intvalores:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Obviamente, o CPython não vem com um floatvalor pré-criado para 42.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. Como ce dsão definidos em instruções separadas, seus valores não são mesclados. Como ee fsã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, 128e depois invoque dis.dis-a, e você verá que existe um único valor constante(128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Você pode perceber que o compilador armazenou 128como 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:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Coloque isso em uma função, dise observe as co_consts- existem a 1e a 2, duas (1, 2)tuplas que compartilham o mesmo 1e 2mas 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:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Por outro lado, é limitado ao strtipo 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 é:

  • Não escreva código que assuma dois valores imutáveis ​​iguais, mas criados separadamente, são idênticos (não use x is y, use x == y)
  • Não escreva código que assuma dois valores imutáveis ​​iguais, mas criados separadamente, são distintos (não use x is not y, use x != y)

Ou, em outras palavras, use apenas ispara testar os singletons documentados (como None) ou que são criados apenas em um local no código (como o _sentinel = object()idioma).

abarnert
fonte
O conselho menos enigmático é simplesmente: não use x is ypara comparar, use x == y. Da mesma forma não use x is not y, usex != y
smci 9/03
Então, olhando para esta pergunta , por que está a=257; b=257em uma única linha a is bTrue
Joe
8

is é o operador de igualdade de identidade (funcionando como id(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á das isclasses que definem de __eq__maneira absurda:

class Unequal:
    def __eq__(self, other):
        return False

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 Nowvez de mostrar que é uma avaliação com time.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 ==ou is. O CPython define o protocolo numérico , incluindo PyNumber_Check, mas isso não é acessível no próprio Python.

Poderíamos tentar usar isinstancecom 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 base numbers.Number, mas têm o mesmo problema:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

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 . isverifica a identidade do objeto, não valoriza a igualdade. O PHP também tem uma história colorida, onde ===aparentemente se comporta isapenas 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).

Yann Vernier
fonte
4

Isso também acontece com as strings:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Agora tudo parece bem.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Isso também é esperado.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Agora isso é inesperado.

sobolevn
fonte
Aconteceu com isso - concordou, que ainda mais estranho. Então eu brinquei com isso, e é ainda mais estranho - relacionado ao espaço. Por exemplo, a sequência 'xx'é como o esperado, como é 'xxx', mas 'x x'não é.
Brian
2
Isso ocorre porque parece um símbolo se não houver espaço nele. Os nomes são automaticamente internados; portanto, se houver algo nomeado xxem 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-interning
Yann Vernier
3

Novidades no Python 3.8: Alterações no comportamento do Python :

O compilador agora produz um SyntaxWarning quando verificações de identidade ( ise is not) são usadas com certos tipos de literais (por exemplo, strings, ints). Geralmente, eles podem funcionar acidentalmente no CPython, mas não são garantidos pelas especificações da linguagem. O aviso aconselha os usuários a usar testes de igualdade ( == e !=).

cclauss
fonte