El operador "es" se comporta inesperadamente con enteros

509

¿Por qué lo siguiente se comporta inesperadamente en 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

Estoy usando Python 2.5.2. Al probar algunas versiones diferentes de Python, parece que Python 2.3.3 muestra el comportamiento anterior entre 99 y 100.

Basado en lo anterior, puedo plantear la hipótesis de que Python se implementa internamente de modo que los enteros "pequeños" se almacenan de manera diferente a los enteros más grandes y el isoperador puede notar la diferencia. ¿Por qué la abstracción permeable? ¿Cuál es una mejor manera de comparar dos objetos arbitrarios para ver si son iguales cuando no sé de antemano si son números o no?

Greg Hewgill
fuente
1
Echa un vistazo aquí > La implementación actual mantiene una matriz de objetos enteros para todos los> enteros entre -5 y 256, cuando creas un int en ese rango, en realidad solo obtienes una referencia al objeto existente.
user5319825
2
Este es un detalle de implementación específico de CPython y un comportamiento indefinido, use con precaución
ospider

Respuestas:

393

Mira esto:

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

Esto es lo que encontré en la documentación de Python 2, "Objetos enteros simples" (es lo mismo para Python 3 ):

La implementación actual mantiene una matriz de objetos enteros para todos los enteros entre -5 y 256, cuando crea un int en ese rango, en realidad solo obtiene una referencia al objeto existente. Por lo tanto, debería ser posible cambiar el valor de 1. Sospecho que el comportamiento de Python en este caso no está definido. :-)

Cybis
fuente
46
¿Alguien sabe cómo se eligió ese rango (-5, 256)? No me sorprendería demasiado si fuera (0, 255) o incluso (-255, 255), pero un rango de 262 números que comienzan en -5 parece sorprendentemente arbitrario.
Woodrow Barlow el
66
@WoodrowBarlow: Creo que el -5 es solo una heurística para capturar marcadores de posición negativos comunes. 0..255 cubre matrices de valores de un solo byte. 256 es misterioso, pero supongo que es para (des) ensamblar enteros en / desde bytes.
Davis Herring
3
Por lo que entiendo, el rango se eligió al observar los valores comúnmente utilizados en múltiples proyectos (y múltiples idiomas).
Tony Suffolk 66
99
De acuerdo con reddit.com/r/Python/comments/18leav/… , el rango solía ser [-5,100]. Se amplió para incluir el rango completo de valores de bytes, más 256, porque presumiblemente es un número común.
mwfearnley
2
@Ashwani intenta leer los comentarios justo al lado de tu comentario, publicado dos años antes que el tuyo, y encontrarás la respuesta a tu pregunta.
jbg
116

El operador "es" de Python se comporta inesperadamente con enteros?

En resumen, permítanme enfatizar: no lo use ispara comparar enteros.

Este no es un comportamiento sobre el que deberías tener expectativas.

En su lugar, use ==y !=para comparar la igualdad y la desigualdad, respectivamente. Por ejemplo:

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

Explicación

Para saber esto, necesita saber lo siguiente.

Primero, ¿qué hace is? Es un operador de comparación. De la documentación :

Los operadores isy la is notprueba de identidad del objeto: x is yes verdadero si y solo si x e y son el mismo objeto. x is not yproduce el valor de verdad inverso.

Y así, los siguientes son equivalentes.

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

De la documentación :

id Devuelve la "identidad" de un objeto. Este es un entero (o entero largo) que se garantiza que es único y constante para este objeto durante su vida útil. Dos objetos con vidas que no se superponen pueden tener el mismo id()valor.

Tenga en cuenta que el hecho de que la identificación de un objeto en CPython (la implementación de referencia de Python) es la ubicación en la memoria es un detalle de implementación. Otras implementaciones de Python (como Jython o IronPython) podrían tener fácilmente una implementación diferente paraid .

Entonces, ¿para qué es el caso de uso is? PEP8 describe :

Las comparaciones con singletons como Nonesiempre deben hacerse con iso is not, nunca con los operadores de igualdad.

La pregunta

Usted hace y declara la siguiente pregunta (con código):

¿Por qué lo siguiente se comporta inesperadamente en Python?

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

Es no un resultado esperado. ¿Por qué se espera? Solo significa que los enteros valorados en 256referenciados por ambos ay bson la misma instancia de entero. Los enteros son inmutables en Python, por lo tanto no pueden cambiar. Esto no debería tener impacto en ningún código. No debe esperarse. Es simplemente un detalle de implementación.

Pero quizás deberíamos alegrarnos de que no haya una nueva instancia separada en la memoria cada vez que establezcamos un valor igual a 256.

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

Parece que ahora tenemos dos instancias separadas de enteros con el valor de 257en memoria. Como los enteros son inmutables, esto desperdicia memoria. Esperemos que no estemos desperdiciando mucho. Probablemente no lo estemos. Pero este comportamiento no está garantizado.

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

Bueno, esto parece que su implementación particular de Python está tratando de ser inteligente y no está creando enteros con valor redundante en la memoria a menos que sea necesario. Parece indicar que está utilizando la implementación de referencia de Python, que es CPython. Bueno para CPython.

Podría ser aún mejor si CPython pudiera hacer esto globalmente, si pudiera hacerlo a bajo costo (ya que habría un costo en la búsqueda), tal vez otra implementación podría hacerlo.

Pero en cuanto al impacto en el código, no debería importarle si un entero es una instancia particular de un entero. Solo debe importarle cuál es el valor de esa instancia, y usaría los operadores de comparación normales para eso, es decir ==.

Que ishace

iscomprueba que los iddos objetos son iguales. En CPython, ides la ubicación en la memoria, pero podría ser algún otro número de identificación único en otra implementación. Para reformular esto con código:

>>> a is b

es lo mismo que

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

¿Por qué querríamos usar isentonces?

Esto puede ser una verificación muy rápida en relación con decir, verificando si dos cadenas muy largas tienen el mismo valor. Pero como se aplica a la unicidad del objeto, tenemos casos de uso limitados para él. De hecho, en su mayoría queremos usarlo para verificar None, que es un singleton (una única instancia que existe en un lugar en la memoria). Podríamos crear otros singletons si existe el potencial de combinarlos, con lo que podríamos verificar is, pero estos son relativamente raros. Aquí hay un ejemplo (funcionará en Python 2 y 3) ej.

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

Y así vemos, con isy un centinela, somos capaces de diferenciar cuándo barse llama sin argumentos y cuándo se llama con None. Estos son los principales casos de uso para is: no lo use para probar la igualdad de enteros, cadenas, tuplas u otras cosas como estas.

Aaron Hall
fuente
"Estos son los principales casos de uso para is- no lo use para probar la igualdad de enteros, cadenas, tuplas u otras cosas como estas". Sin embargo, estoy tratando de integrar una máquina de estado simple en mi clase, y dado que los estados son valores opacos cuya única propiedad observable es la de ser idénticos o diferentes, parece bastante natural que sean comparables con ellos is. Planeo usar cadenas internas como estados. Hubiera preferido enteros simples, pero desafortunadamente Python no puede integrar enteros ( 0 is 0es un detalle de implementación).
Alexey
@Alexey parece que necesitas enumeraciones? stackoverflow.com/questions/37601644/…
Aaron Hall
Quizás, gracias, no sabía de ellos. Esta podría ser una adición apropiada para responder IMO.
Alexey
Quizás usar una cantidad de objetos tontos como el centinela en su respuesta sería una solución más liviana ...
Alexey
Las enumeraciones de @Alexey están en la biblioteca estándar de Python 3, y eso probablemente alentaría a su código a ser un poco más significativo que los centinelas desnudos.
Aaron Hall
60

Depende de si estás buscando ver si 2 cosas son iguales o el mismo objeto.

iscomprueba si son el mismo objeto, no solo iguales. Las entradas pequeñas probablemente apuntan a la misma ubicación de memoria para ahorrar espacio

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

Debe usar ==para comparar la igualdad de objetos arbitrarios. Puede especificar el comportamiento con los atributos __eq__y __ne__.

JimB
fuente
¡Felicitaciones por explicar cómo comparar objetos arbitrarios, como preguntó el OP!
Joooeey
54

Llego tarde pero, ¿quieres alguna fuente con tu respuesta? Intentaré redactar esto de manera introductoria para que más personas puedan seguirlo.


Lo bueno de CPython es que realmente puedes ver la fuente de esto. Voy a usar enlaces para la versión 3.5 , pero encontrar los correspondientes 2.x es trivial.

En CPython, la función C-API que maneja la creación de un nuevo intobjeto es PyLong_FromLong(long v). La descripción de esta función es:

La implementación actual mantiene una matriz de objetos enteros para todos los enteros entre -5 y 256, cuando crea un int en ese rango, en realidad solo obtiene una referencia al objeto existente . Por lo tanto, debería ser posible cambiar el valor de 1. Sospecho que el comportamiento de Python en este caso no está definido. :-)

(Mi cursiva)

No sé sobre ti, pero veo esto y pienso: ¡encontremos esa matriz!

Si no ha jugado con el código C que implementa CPython , debería hacerlo ; todo es bastante organizado y legible. Para nuestro caso, necesitamos mirar en elObjects subdirectorio del árbol de directorios del código fuente principal .

PyLong_FromLongtrata con longobjetos, por lo que no debería ser difícil deducir que necesitamos echar un vistazo dentro longobject.c. Después de mirar dentro, puede pensar que las cosas son caóticas; son, pero no temas, la función que estamos buscando es escalofriante línea 230 esperando que la revisemos. Es una función pequeña, por lo que el cuerpo principal (excluyendo las declaraciones) se pega fácilmente aquí:

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; 
}

Ahora, no somos C master-code-haxxorz pero tampoco somos tontos, podemos ver que CHECK_SMALL_INT(ival);nos mira a todos seductoramente; Podemos entender que tiene algo que ver con esto.Vamos a ver:

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

Entonces, es una macro que llama a la función get_small_intsi el valorival cumple la condición:

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

Entonces, ¿qué son NSMALLNEGINTSy NSMALLPOSINTS? Macros! Aquí están :

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

Entonces nuestra condición es if (-5 <= ival && ival < 257) llamadaget_small_int .

A continuación, veamos get_small_inten todo su esplendor (bueno, solo veremos su cuerpo porque ahí es donde están las cosas interesantes):

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

De acuerdo, declarar un PyObject , afirme que la condición anterior se cumple y ejecute la asignación:

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

small_intsse parece mucho a esa matriz que hemos estado buscando, ¡y lo es! ¡Podríamos haber leído la maldita documentación y lo habríamos sabido todo el tiempo!:

/* 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];

Así que sí, este es nuestro chico. Cuando quieras crear un nuevoint en el rango[NSMALLNEGINTS, NSMALLPOSINTS) , simplemente obtendrá una referencia a un objeto ya existente que ha sido previamente asignado.

Como la referencia se refiere al mismo objeto, la emisión id() directamente o verificar la identidad conis él devolverá exactamente lo mismo.

Pero, ¿cuándo se asignan?

Durante la inicialización en_PyLong_Init Python con mucho gusto entrará en un bucle for, haga esto por usted:

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

¡Mira la fuente para leer el cuerpo del bucle!

Espero que mi explicación te haya aclarado las cosas ahora (juego de palabras obviamente intencionado).


Pero, 257 is 257 ? ¿Qué pasa?

En realidad, esto es más fácil de explicar, y ya he intentado hacerlo ; se debe al hecho de que Python ejecutará esta declaración interactiva como un solo bloque:

>>> 257 is 257

Durante la compilación de esta declaración, CPython verá que tiene dos literales coincidentes y usará la misma PyLongObjectrepresentación 257. Puede ver esto si hace la compilación usted mismo y examina su contenido:

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

Cuando CPython realiza la operación, ahora solo va a cargar exactamente el mismo 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)

Entonces isvolveremos True.

Dimitris Fasarakis Hilliard
fuente
37

Como puedes comprobar en el archivo fuente intobject.c , Python almacena en caché enteros pequeños para mayor eficiencia. Cada vez que crea una referencia a un entero pequeño, hace referencia al entero pequeño en caché, no a un objeto nuevo. 257 no es un entero pequeño, por lo que se calcula como un objeto diferente.

Es mejor usar ==para ese propósito.

Ángel
fuente
19

Creo que tus hipótesis son correctas. Experimente con id(identidad del 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 los números <= 255se tratan como literales y todo lo anterior se trata de manera diferente!

Amit
fuente
1
Esto se debe a que los objetos que representan valores de -5 a +256 se crean en el tiempo de inicio, por lo que todo uso de esos valores se usa para preconstruir objetos. Casi todas las referencias a enteros fuera de ese rango crean un nuevo objeto interno cada vez que se hace referencia a ellas. Creo que el uso del término literal es confuso: literal se refiere normalmente a cualquier valor que se escribe en un fragmento de código, por lo que todos los números en el código fuente son literales.
Tony Suffolk 66
13

Para objetos de valor inmutable, como ints, strings u datetime, la identidad del objeto no es especialmente útil. Es mejor pensar en la igualdad. La identidad es esencialmente un detalle de implementación para los objetos de valor, ya que son inmutables, no hay una diferencia efectiva entre tener múltiples referencias al mismo objeto u objetos múltiples.

babbageclunk
fuente
12

Hay otro problema que no se señala en ninguna de las respuestas existentes. Python puede fusionar dos valores inmutables, y los valores int pequeños creados previamente no son la única forma en que esto puede suceder. Nunca se garantiza que una implementación de Python haga esto, pero todos lo hacen por algo más que pequeños ints.


Por un lado, hay algunos otros valores creados previamente, como el vacío tuple , stry bytes, y algunas cadenas cortas (en CPython 3.6, que es la única de 256 caracteres cadenas Latin-1). Por ejemplo:

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

Pero también, incluso los valores no creados previamente pueden ser idénticos. Considere estos ejemplos:

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

Y esto no se limita a los intvalores:

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

Obviamente, CPython no viene con un floatvalor pre-creado para 42.23e100. Entonces, ¿qué está pasando aquí?

El compilador CPython se fusionará valores constantes de algunos tipos conocida como inmutables int, float, str, bytes, en la misma unidad de compilación. Para un módulo, todo el módulo es una unidad de compilación, pero en el intérprete interactivo, cada declaración es una unidad de compilación separada. Como cy dse definen en declaraciones separadas, sus valores no se fusionan. Ya quee y fse definen en la misma declaración, sus valores se fusionan.


Puede ver lo que está sucediendo desmontando el código de bytes. Intente definir una función que lo haga e, f = 128, 128y luego invocarla dis.dis, y verá que hay un ú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

Puede notar que el compilador se ha almacenado 128como una constante a pesar de que el bytecode no lo utiliza realmente, lo que le da una idea de la poca optimización que hace el compilador de CPython. Lo que significa que las tuplas (no vacías) en realidad no terminan fusionadas:

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

Póngalo en una función, disy mire co_consts: hay a 1y a 2, dos (1, 2)tuplas que comparten lo mismo 1y que 2no son idénticas, y un((1, 2), (1, 2)) tupla que tiene las dos tuplas iguales distintas.


Hay una optimización más que hace CPython: internación de cadenas. A diferencia del plegado constante del compilador, esto no está restringido a literales de código fuente:

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

Por otro lado, se limita al strtipo y a cadenas de tipo de almacenamiento interno "ascii compact", "compact" o "legacy ready" , y en muchos casos solo se internará "ascii compact".


En cualquier caso, las reglas sobre qué valores deben ser, pueden ser o no distintos pueden variar de una implementación a otra, y entre versiones de la misma implementación, y tal vez incluso entre ejecuciones del mismo código en la misma copia de la misma implementación .

Puede valer la pena aprender las reglas para un Python específico por diversión. Pero no vale la pena confiar en ellos en su código. La única regla segura es:

  • No escriba código que suponga que dos valores inmutables iguales pero creados por separado son idénticos (no use x is y , use x == y)
  • No escriba código que suponga que dos valores inmutables iguales pero creados por separado son distintos (no use x is not y, usex != y )

O, en otras palabras, solo use ispara probar los singletons documentados (como None) o que solo se crean en un lugar en el código (como el _sentinel = object()idioma).

abarnert
fuente
El consejo menos críptico es simplemente: no usar x is ypara comparar, usar x == y. Del mismo modo, no use x is not y, usex != y
smci
Mirando esta pregunta , ¿por qué está a=257; b=257en una sola línea? a is bVerdadero
Joe
8

is es el operador de igualdad de identidad (funciona como id(a) == id(b)); es solo que dos números iguales no son necesariamente el mismo objeto. Por razones de rendimiento, algunos números enteros pequeños se recuerdan por lo que tienden a ser los mismos (esto se puede hacer ya que son inmutables).

El === operador de PHP , por otro lado, se describe como comprobación de igualdad y tipo: x == y and type(x) == type(y)según el comentario de Paulo Freitas. Esto será suficiente para los números comunes, pero difiere de las isclases que definen __eq__de manera absurda:

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

Aparentemente, PHP permite lo mismo para las clases "integradas" (que entiendo que se implementan a nivel C, no en PHP). Un uso un poco menos absurdo podría ser un objeto temporizador, que tiene un valor diferente cada vez que se usa como un número. Por qué querría emular Visual BasicNow lugar de mostrar que es una evaluación contime.time() No lo sé.

Greg Hewgill (OP) hizo un comentario aclaratorio "Mi objetivo es comparar la identidad del objeto, en lugar de la igualdad de valor. Excepto para los números, donde quiero tratar la identidad del objeto igual que la igualdad de valor".

Esto tendría otra respuesta, ya que tenemos que clasificar las cosas como números o no, para seleccionar si nos comparamos con ==o is. CPython define el protocolo de número , incluido PyNumber_Check, pero esto no es accesible desde Python.

Podríamos intentar usarlo isinstancecon todos los tipos de números que conocemos, pero esto inevitablemente estaría incompleto. El módulo de tipos contiene una lista StringTypes pero no NumberTypes. Desde Python 2.6, las clases de números incorporadas tienen una clase base numbers.Number, pero tiene el mismo problema:

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

Por cierto, NumPy producirá instancias separadas de números bajos.

En realidad no sé una respuesta a esta variante de la pregunta. Supongo que uno podría usar teóricamente ctypes para llamar PyNumber_Check, pero incluso esa función ha sido debatida , y ciertamente no es portátil. Tendremos que ser menos particulares sobre lo que probamos por ahora.

Al final, este problema se debe a que Python originalmente no tenía un árbol de tipos con predicados como Scheme number? o la clase de tipo Num de Haskell . iscomprueba la identidad del objeto, no la igualdad de valores. PHP también tiene una historia colorida, donde ===aparentemente se comporta issolo en objetos en PHP5, pero no en PHP4 . Tales son los crecientes dolores de moverse a través de los idiomas (incluidas las versiones de uno).

Yann Vernier
fuente
4

También sucede con cadenas:

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

Ahora todo parece estar bien.

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

Eso también se espera.

>>> 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)

Ahora eso es inesperado.

sobolevn
fuente
Sucedió esto, de acuerdo, que incluso más raro. Así que jugué con él, y aún es más extraño, relacionado con el espacio. Por ejemplo, la cadena 'xx'es como se esperaba, como es 'xxx', pero 'x x'no lo es.
Brian
2
Eso es porque parece un símbolo si no hay espacio en él. Los nombres se internan automáticamente, por lo que si hay algo nombrado xxen alguna parte de su sesión de Python, esa cadena ya está internada; y puede haber una heurística que lo haga si solo se parece a un nombre. Al igual que con los números, esto se puede hacer porque son inmutables. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interning
Yann Vernier
3

Novedades de Python 3.8: Cambios en el comportamiento de Python :

El compilador ahora produce un SyntaxWarning cuando las verificaciones de identidad ( isy is not) se usan con ciertos tipos de literales (por ejemplo, cadenas, ints). A menudo, estos pueden funcionar por accidente en CPython, pero no están garantizados por la especificación del idioma. La advertencia aconseja a los usuarios que usen pruebas de igualdad ( == y !=) en su lugar.

cclauss
fuente