2012-07-13 26 views
127

Duplicar posible:
Python “is” operator behaves unexpectedly with integers¿Por qué (0-6) es -6 = Falso?

Hoy me trató de depurar mi proyecto y después de unas horas de análisis que había conseguido esto:

>>> (0-6) is -6 
False 

pero,

>>> (0-5) is -5 
True 

¿Podría explicarme por qué? Tal vez esto es algún tipo de error o comportamiento muy extraño.

> Python 2.7.3 (default, Apr 24 2012, 00:00:54) [GCC 4.7.0 20120414 (prerelease)] on linux2 
>>> type(0-6) 
<type 'int'> 
>>> type(-6) 
<type 'int'> 
>>> type((0-6) is -6) 
<type 'bool'> 
>>> 
+6

bien eso es francamente loco – Wug

+25

¿Qué te llevó a usar 'is' en primer lugar? No es algo que se deba usar a menudo en Python, aparte del caso 'is/is not None'. –

+3

@ El comentario de Russel da en el clavo: el problema es que aparentemente alguien estaba usando "es" para comparar números y esperaba que funcionara como '=', una expectativa incorrecta. – LarsH

Respuesta

150

Todos los números enteros de -5 a 256 inclusive son guardados como objetos globales que comparten la misma dirección con CPython, por lo tanto pasa la prueba is.

Este artefacto se explica en detalle en http://www.laurentluce.com/posts/python-integer-objects-implementation/, y podemos verificar el código fuente actual en http://hg.python.org/cpython/file/tip/Objects/longobject.c.

Una estructura específica se utiliza para referir enteros pequeños y compartirlos para que el acceso sea rápido. Es una matriz de 262 punteros a objetos enteros. Esos objetos enteros se asignan durante la inicialización en un bloque de objetos enteros que vimos arriba. El rango de enteros pequeños es de -5 a 257. Muchos programas de Python pasan mucho tiempo usando enteros en ese rango, por lo que esta es una decisión inteligente.

Esto es solo un detalle de implementación de CPython y no debe confiar en esto. Por ejemplo, PyPy implementó el id del número entero para devolverse, por lo que (0-6) is -6 es siempre verdadero incluso si son "objetos diferentes" internamente; también le permite configurar si habilitar este almacenamiento en caché entero, e incluso establecer los límites inferior y superior. Pero, en general, los objetos recuperados de diferentes orígenes no serán idénticos. Si desea comparar la igualdad, simplemente use ==.

+2

Eso es lo que iba a decir, pero no pude pronunciarlo correctamente. +1 – mpen

+1

Interesante sesgo hacia el lado positivo. El artículo dice que "muchos programas de Python pasan mucho tiempo usando enteros en ese rango", por lo que los desarrolladores probablemente lo han medido de alguna manera. Supongo que los literales de números negativos solo se usan para los códigos de error actualmente ... –

+0

Gracias @KennyTM. –

26

No es un error. is no es una prueba de igualdad. == dará los resultados esperados.

El motivo técnico de este comportamiento es que una implementación de Python es libre de tratar diferentes instancias del mismo valor constante como el mismo objeto o como objetos diferentes. La implementación de Python que está utilizando elige hacer que ciertas constantes pequeñas compartan el mismo objeto por razones de ahorro de memoria. No puede confiar en que este comportamiento sea la misma versión a la versión o en diferentes implementaciones de Python.

+2

> 'is' no es una prueba de igualdad. Esta. 'is' es una prueba de identidad, para ver si dos objetos son exactamente los mismos. Sucede que en la implementación de CPython, algunos objetos int se almacenan en caché. – Darthfett

+1

+1 Para un desarrollador de Python ninguno, esto me lo explica mejor. –

29

Python almacena números enteros en el rango -5 - 256 en el intérprete: tiene un conjunto de objetos enteros de los que se devuelven estos enteros. Es por eso que esos objetos son los mismos: (0-5) y -5, pero no (0-6) y -6, ya que se crean en el acto.

Aquí está la fuente en el código fuente de CPython:

#define NSMALLPOSINTS   257 
#define NSMALLNEGINTS   5 
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; 

(view CPython source code: /trunk/Objects/intobject.c).El código fuente incluye el siguiente comentario:

/* References to small integers are saved in this array so that they 
    can be shared. 
    The integers that are saved are those in the range 
    -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). 
*/ 

El operador is comparará entonces ellos (-5) como iguales porque son el mismo objeto (misma posición de memoria), pero los otros dos nuevos números enteros (-6) será a las diferentes ubicaciones de memoria (y luego is no devolverá True). Tenga en cuenta que 257 en el código fuente anterior es para los enteros positivos, por lo que es 0 - 256 (inclusive).

(source)

16

Lo hacen porque CPython almacena en caché algunos números enteros pequeños y pequeñas cadenas y le da a cada instancia de ese objeto una misma id().

(0-5) y -5 tiene mismo valor para id(), lo cual no es cierto para 0-6 y -6

>>> id((0-6)) 
12064324 
>>> id((-6)) 
12064276 
>>> id((0-5)) 
10022392 
>>> id((-5)) 
10022392 

mismo modo para las cadenas:

>>> x = 'abc' 
>>> y = 'abc' 
>>> x is y 
True 
>>> x = 'a little big string' 
>>> y = 'a little big string' 
>>> x is y 
False 

Para más detalles sobre el almacenamiento en caché de cadena, debe decir: is operator behaves differently when comparing strings with spaces

+2

entonces ¿por qué '-6' se considera" grande "y' -5' no? ¿Cuál es el criterio de calificación para que algo se considere "grande"? – inspectorG4dget

+1

Para CPython, de -5 a 256 están "internados" (en caché). Es una elección de implementación algo arbitraria. Si se utiliza mucho un objeto interno dado, existe un ahorro de memoria potencialmente grande, pero su administración tiene un costo (ya sea en tiempo de ejecución o en la memoria) por lo que no es conveniente hacerlo para todo. –

+0

+1 para mostrar los ID; Estaba a punto de agregar eso a mi respuesta. –

Cuestiones relacionadas