Pergunta

Por que faz o seguinte comportamento inesperado 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

Eu estou usando 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 supor que Python é internamente implementado de tal forma que "pequenos" inteiros são armazenados de forma diferente do que números inteiros maiores eo operador is pode dizer a diferença. Por que a abstração de fuga? Qual é a melhor maneira de comparar dois objetos arbitrários para ver se eles são o mesmo quando eu não sei com antecedência se eles são números ou não?

Foi útil?

Solução

Dê uma olhada nisso:

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

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

A implementação atual mantém uma matriz de número inteiro objetos para todos inteiros entre -5 e 256, quando você criar um int em que faixa de você na verdade, é só pegar de volta uma referência a o objeto existente. Por isso deve ser possível alterar o valor de 1. I suspeitar que o comportamento do Python em Neste caso é indefinido. : -)

Outras dicas

Python do “é” operador tem um comportamento inesperado com números inteiros?

.

Em resumo - deixe-me enfatizar: Não use is para comparar inteiros

Este não é o comportamento que você deve ter quaisquer expectativas sobre.

Em vez disso, o uso == e != para comparar por 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.

Em primeiro lugar, o que is fazer? É um operador de comparação. Do documentação :

Os operadores is e teste is not para a identidade do objeto: x is y é verdade Se e só se x e y são o mesmo objecto. x is not y rendimentos dos valor verdade inverso.

E assim, a seguir são equivalentes.

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

A partir da documentação :

id Voltar a “identidade” de um objeto. Este é um inteiro (ou longo número inteiro), que está garantido para ser única e constante para este objecto durante o seu tempo de vida. Dois objectos com tempos de vida não sobrepostos pode têm o mesmo valor id().

Note que o fato de que o ID de um objeto em CPython (a implementação de referência do Python) é o local na memória é um detalhe de implementação. Outras implementações do Python (como Jython ou IronPython) poderia facilmente ter uma implementação diferente para id.

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

Comparações com singletons como None deve sempre ser feito com is ou is not, nunca os operadores de igualdade.

A Pergunta

Você pergunta, e do estado, a seguinte pergunta (com código):

Por que faz o seguinte comportamento inesperado em Python?

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

É não um resultado esperado. Por que é esperado? Significa apenas que os números inteiros avaliados em 256 referenciado por ambos a e b são o mesmo número inteiro de exemplo. Inteiros são imutáveis ??em Python, portanto, eles não podem mudar. Este não deve ter nenhum impacto sobre qualquer código. Ele não deve ser esperado. É apenas um detalhe de implementação.

Mas talvez deveríamos ser gratos de que não há uma nova instância separada na memória cada vez que declarar 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 257 na memória. Desde inteiros são imutáveis, esta memória resíduos. Vamos esperar que não estamos perdendo muito dele. Nós provavelmente não é. Mas esse comportamento não é garantida.

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

Bem, isso se parece com a sua implementação particular de Python está tentando ser inteligente e não a criação de números inteiros de forma redundante valorizados na memória a menos que ele tem para. Você parece indicar que você está usando a implementação referente de Python, que é CPython. Bom para CPython.

Pode ser ainda melhor se CPython poderia fazer isso globalmente, se pudesse fazê-lo mais barato (como haveria um custo na pesquisa), talvez um outro poder de implementação.

Mas, como para o impacto no código, você não deveria se preocupar se um inteiro é um caso particular de um inteiro. Você só deve importar com o que o valor dessa instância é, e você usar os operadores normais de comparação, para isso, ou seja ==.

O que is faz

cheques is que o id de dois objetos são os mesmos. Em CPython, o id é a localização na memória, mas poderia ser algum outro número identificar inequivocamente em outra aplicação. Para reafirmar isso com código:

>>> a is b

é o mesmo que

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

Por que nós queremos usar is então?

Este pode ser um muito rápido verificar em relação ao dizer, verificando se duas cordas muito longas são iguais em valor. Mas uma vez que se aplica à singularidade do objeto, temos, portanto, casos de uso limitadas para ele. Na verdade, a maioria quer usá-lo para verificar se há None, que é um Singleton (única instância existente em um lugar na memória). Poderíamos criar outros singletons se há potencial para confundir-los, o que podemos verificar com is, mas estes são relativamente raros. Aqui está um exemplo (vai trabalhar 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 impressões:

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

E assim vemos, com is e uma sentinela, somos capazes de diferenciar quando bar é chamado sem argumentos e quando ele é chamado com None. Estes são os casos de uso primários para is - fazer não usá-lo para teste de igualdade de inteiros, strings, tuplas, ou outras coisas como estes

.

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

cheques is para ver se eles são o mesmo objeto, não apenas iguais. As pequenas ints 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 igualdade de objetos arbitrários. Você pode especificar o comportamento com o __eq__, e atributos __ne__.

Eu estou atrasado, mas, você quer alguma fonte com a sua resposta? *

Boa coisa sobre CPython é que você pode realmente ver a fonte para isso. Eu estou indo para ligações de uso para a liberação 3.5 por agora; encontrar os 2.x correspondentes é trivial.

Em CPython, a função C-API que lida com a criação de um novo objeto int é PyLong_FromLong(long v) . A descrição para esta função é a seguinte:

A implementação atual mantém uma matriz de objetos inteiros para todos os inteiros entre -5 e 256, quando você cria um int nesse intervalo que você realmente apenas voltar uma referência para o objeto existente . Por isso, deve ser possível alterar o valor de 1. Eu suspeito que o comportamento do Python, neste caso, é indefinido. : -)

Não sei sobre você, mas eu vejo isso e pensar: Vamos achado que array

Se você não brincou com o código C implementação de CPython deverá , tudo é muito organizado e legível. Para o nosso caso, precisamos olhar no Objects/ subdiretório do principal código fonte árvore de diretórios .

PyLong_FromLong lida com long objetos por isso não deve ser difícil deduzir que precisamos dentro espiada longobject.c . Depois de olhar dentro de você pode pensar que as coisas são caóticos; eles são, mas não medo, a função que estamos procurando é arrepiante em line 230 esperando por nós para check-out. É uma função pequeno assim que o corpo principal (excluídas 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ós não somos C mestre-code-haxxorz , mas nós também não está mudo, podemos ver que CHECK_SMALL_INT(ival); espreitar-nos a todos sedutoramente; podemos entender que tem algo a ver com isso. verificação do Let it out:

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

Portanto, é uma macro que a função chamadas get_small_int se satisfaz valor ival a condição:

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

Então, quais são NSMALLNEGINTS e NSMALLPOSINTS? Se você adivinhou macros você não ganha nada porque não era uma pergunta tão difícil .. De qualquer forma, aqui estão elas :

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

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

Não há outro lugar para ir, mas continuamos nossa jornada, olhando para get_small_int em toda a sua glória (bem, vamos apenas olhar para ele do corpo porque é isso eram as coisas interessantes estão):

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

Ok, declarar uma PyObject, afirmam que a condição anterior detém e executar o serviço:

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

small_ints parece muito com essa matriz que temos vindo a procurar .. e, é! Poderíamos ter apenas ler a documentação mínima e gostaríamos 've saber tudo junto :

/* 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, esta é a nossa cara. Quando você quiser criar um novo int na [NSMALLNEGINTS, NSMALLPOSINTS) gama você só vai receber de volta uma referência a um objeto já existente que tenha sido pré-alocados.

Uma vez que a referência se refere ao mesmo object, emitindo id() direta ou verificação de identidade com is sobre ele irá retornar exatamente a mesma coisa.

Mas, quando eles são alocados ??

Durante a inicialização em _PyLong_Init Python o prazer de introduzir em um loop faça isso para você:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {
    // Look me up!
}

Espero que minha explicação te fez C (trocadilho obviamente intented) as coisas claramente agora.


Mas, 257 é 257? O que houve?

Este é realmente mais fácil de explicar, e tentei fazê-lo já ; é devido ao fato de que o Python irá executar esta declaração interativa:

>>> 257 is 257

como um único bloco. Durante complilation desta declaração, CPython vai ver que você tem dois literais correspondentes e usará o mesmo PyLongObject representando 257. Você pode ver isso se você fizer a compilação si mesmo e examinar seu conteúdo:

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

Quando CPython faz a operação; agora está indo só para carregar 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)

Assim is voltará True.


. * - Vou tentar palavra isso de uma maneira mais introdutório para que mais para ser capaz de seguir junto

Como você pode check-in intobject.c , Python caches pequenos números inteiros de eficiência. Toda vez que você criar uma referência a um pequeno número inteiro, você está se referindo ao pequeno número inteiro em cache, não um novo objeto. 257 não é um pequeno número inteiro, de modo que é calculado como um objecto diferente.

É melhor uso == para o efeito.

Eu acho que suas hipóteses está correta. 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 <= 255 são tratados como literais e qualquer coisa acima é tratado de forma diferente!

Para objetos de valor imutáveis, como ints, cordas ou datetimes, a identidade do objeto não é especialmente útil. É melhor pensar em igualdade. Identidade é, essencialmente, um detalhe de implementação de objetos de valor -. Como eles são imutáveis, não há diferença efetiva entre ter vários refs ao mesmo objeto ou vários objetos

is é o operador de igualdade de identidade (funcionando como id(a) == id(b)); é só que dois números iguais não são necessariamente o mesmo objeto. Por motivos de desempenho alguns pequenos números inteiros que ser memoized para que eles tendem a ser o mesmo (isto pode ser feito, uma vez que são imutáveis).

operador === do PHP, por outro lado, é descrita como a verificação da igualdade e digite: x == y and type(x) == type(y) como por comentário Paulo Freitas'. Isso será suficiente para números comuns, mas diferem de is para as classes que definem __eq__ de forma absurda:

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

PHP aparentemente permite que a mesma coisa para "built-in" classes (que eu tomar para dizer implementado a nível C, não em PHP). A utilização ligeiramente menos absurda pode ser um objeto de timer, que tem um valor diferente a cada vez que ele é usado como um número. Muito por que você gostaria de imitar Now Visual Basic em vez de mostrar que é uma avaliação com time.time() Eu não sei.

Greg Hewgill (OP) fez um comentário esclarecer "Meu objetivo é comparar a identidade do objeto, ao invés de igualdade de valor. Com exceção de números, onde eu quero identidade deleite objeto o mesmo que a igualdade de valor."

Esta teria ainda outra resposta, como temos de categorizar as coisas como números ou não, para selecionar se compararmos com == ou is. CPython define o número de protocolo , incluindo PyNumber_Check, mas isso não é acessível a partir do próprio Python.

Nós poderíamos tentar usar isinstance com todos os tipos de números que conhecemos, mas isso seria inevitavelmente incompleta. O módulo de tipos contém uma lista StringTypes mas não NumberTypes. Desde Python 2.6, o construído em aulas número tem uma classe base numbers.Number , mas tem o mesmo problema:

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

A propósito, NumPy irá produzir instâncias separadas de números baixos.

Eu realmente não sei a resposta para esta variante da questão. Suponho que uma teoricamente poderia usar ctypes para PyNumber_Check chamada, mas mesmo que a função tem sido debatida , e certamente não é portátil. Nós vamos ter que ser menos específico sobre o que testar agora.

No final, esta questão decorre de Python originalmente não ter uma árvore de tipo com predicados como number? do esquema, ou Haskell é tipo de classe Num . cheques is identidade de objeto, não o valor da igualdade. PHP tem uma história colorida, bem como, onde === aparentemente se comporta como is apenas em objetos em PHP5, mas não PHP4 . Tais são as dores do crescimento de mover-se através das línguas (incluindo versões de um).

Há uma outra questão que não é apontado em qualquer uma das respostas existentes. Python é permitido para fundir quaisquer dois valores imutáveis, e pré-criado pequenos valores int não são a única maneira isso pode acontecer. implementação A Python é nunca garantida para fazer isso, mas todos eles fazê-lo por mais do que apenas pequenas ints.


Por um lado, existem alguns outros valores pré-criados, como o tuple vazio, str e 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, valores ainda não-pré criado 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 a valores int:

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

Obviamente, CPython não vem com um valor float 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 interpretador interativo, cada instrução é uma unidade de compilação separado. Desde c e d são definidos em declarações separadas, seus valores não são mescladas. Desde e e f são definidos na mesma declaração, os seus valores são mesclados.


Você pode ver o que está acontecendo por desmontar o bytecode. Tentar definir uma função que faz e, f = 128, 128 e, em seguida, chamando dis.dis sobre ele, e você verá que há um (128, 128) único valor constante

>>> 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 notar que o compilador tem armazenado 128 como uma constante, mesmo que ele não é realmente usado pelo bytecode, que lhe dá uma idéia de quão pouco otimização do compilador do CPython faz. O que significa que (não-vazia) tuplas realmente não acabam fundiu:

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

Coloque isso em uma função, dis-lo, e olhar para o co_consts-há uma 1 e uma 2, duas tuplas (1, 2) que compartilham o mesmo 1 e 2 mas não são idênticos, e uma tupla ((1, 2), (1, 2)) que tem os dois distintos igual tuplas.


Há mais uma otimização que CPython faz: Internar string. Ao contrário compilador constante de dobragem, este não é restrita a literais código fonte:

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

Por outro lado, é limitado ao tipo str, e cordas de tipo de armazenamento interno "ascii compacto", "compacto", ou "legado pronto" , e em muitos casos apenas "compacto ascii" vai ficar internado.


De qualquer forma, as regras para que valores deve ser, pode ser, ou não pode ser distinto variar de implementação para implementação, e entre as versões da mesma aplicação, e talvez até mesmo entre as execuções do mesmo código na mesma cópia do a mesma implementação.

Pode ser vale a pena aprender as regras para um específico Python para a diversão. Mas não vale a pena confiar neles em seu código. A regra só é seguro é:

  • Do código não escrita que assume dois valores imutáveis ??iguais, mas criados separadamente são idênticas.
  • Do código não escrita que assume dois valores imutáveis ??iguais, mas criados separadamente são distintos.

Ou, em outras palavras, use apenas is de teste para os singletons documentados (como None) ou que só são criados em um único lugar no código (como o idioma _sentinel = object()).

Isso também acontece com 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 é esperado também.

>>> 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 que é inesperado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top