El operador ‘is’ se comporta de manera diferente al comparar cadenas con espacios

Empecé a aprender Python (Python 3.3) y estaba probando el operador. Intenté esto:

 >>> b = 'is it the space?' >>> a = 'is it the space?' >>> a is b False >>> c = 'isitthespace' >>> d = 'isitthespace' >>> c is d True >>> e = 'isitthespace?' >>> f = 'isitthespace?' >>> e is f False 

Parece que el espacio y el signo de interrogación hacen que el comportamiento sea diferente. ¿Que esta pasando?

EDIT: Sé que debería estar usando == , solo quería saber por qué is comporta de esta manera.

Advertencia: esta respuesta trata sobre los detalles de implementación de un intérprete de Python específico. comparando cadenas con is == mala idea.

Bueno, al menos para cpython3.4 / 2.7.3, la respuesta es “no, no es el espacio en blanco”. No solo los espacios en blanco:

  • Dos literales de cadena compartirán memoria si son alfanuméricos o residen en el mismo bloque (archivo, función, clase o comando de un solo intérprete)

  • Una expresión que se evalúa como una cadena dará como resultado un objeto que es idéntico al creado con un literal de cadena, solo si se crea con constantes y operadores binarios / unarios, y la cadena resultante tiene menos de 21 caracteres.

  • Los personajes individuales son únicos.

Ejemplos

Los literales alfanuméricos de cadena siempre comparten memoria:

 >>> x='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' >>> y='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' >>> x is y True 

Los literales de cadena no alfanuméricos comparten memoria si y solo si comparten el bloque sintáctico adjunto:

(Interprete)

 >>> x='`!@#$%^&*() \][=-. >:"?>> z='`!@#$%^&*() \][=-. >:"?>> x is y True >>> x is z False 

(expediente)

 x='`!@#$%^&*() \][=-. >:"?:"? 

Salida: True y False

Para operaciones binarias simples, el comstackdor está haciendo una propagación constante muy simple (ver peephole.c ), pero con cadenas solo lo hace si la cadena resultante es más corta que 21 caracteres. Si este es el caso, las reglas mencionadas anteriormente están en vigor:

 >>> 'a'*10+'a'*10 is 'a'*20 True >>> 'a'*21 is 'a'*21 False >>> 'aaaaaaaaaaaaaaaaaaaaa' is 'aaaaaaaa' + 'aaaaaaaaaaaaa' False >>> t=2; 'a'*t is 'aa' False >>> 'a'.__add__('a') is 'aa' False >>> x='a' ; x+='a'; x is 'aa' False 

Los personajes individuales siempre comparten memoria, por supuesto:

 >>> chr(0x20) is ' ' True 

Para ampliar un poco la respuesta de Ignacio: el operador is es el operador de identidad. Se utiliza para comparar la identidad del objeto . Si construye dos objetos con el mismo contenido, generalmente no es el caso que la identidad del objeto sea verdadera. Funciona para algunas cadenas pequeñas porque CPython, la implementación de referencia de Python, almacena el contenido por separado, haciendo que todos esos objetos hagan referencia al mismo contenido de cadena. Así que el operador devuelve true para aquellos.

Sin embargo, este es un detalle de implementación de CPython y generalmente no está garantizado para CPython ni para ninguna otra implementación. Entonces, usar este hecho es una mala idea, ya que puede romperse cualquier otro día.

Para comparar cadenas, use el operador == que compara la igualdad de los objetos. Dos objetos de cadena se consideran iguales cuando contienen los mismos caracteres. Por lo tanto, este es el operador correcto que se debe utilizar al comparar cadenas, y generalmente is debe evitar si no desea explícitamente la identidad del objeto (ejemplo: a is False ).


Si está realmente interesado en los detalles, puede encontrar la implementación de las cadenas de CPython aquí . Pero nuevamente: este es el detalle de la implementación, por lo que nunca debe exigir que esto funcione.

El operador is basa en la función id , que se guaranteed to be unique among simultaneously existing objects. Específicamente, id devuelve la dirección de memoria del objeto. Parece que CPython tiene direcciones de memoria consistentes para cadenas que contienen solo caracteres az y AZ.

Sin embargo, parece que este solo es el caso cuando la cadena ha sido asignada a una variable:

Aquí, el id de “foo” y el id de a son los mismos. a se ha configurado en “foo” antes de verificar la ID.

 >>> a = "foo" >>> id(a) 4322269384 >>> id("foo") 4322269384 

Sin embargo, la identificación de “barra” y la identificación de a son diferentes cuando se verifica la identificación de “barra” antes de establecer a “barra” igual.

 >>> id("bar") 4322269224 >>> a = "bar" >>> id(a) 4322268984 

Verificando la identificación de “barra” nuevamente después de establecer a “barra” igual a la misma, se obtiene la misma identificación

 >>> id("bar") 4322268984 

Por lo tanto, parece que cPython mantiene direcciones de memoria coherentes para cadenas que contienen solo a-zA-Z cuando esas cadenas se asignan a una variable. También es totalmente posible que esto sea dependiente de la versión: estoy ejecutando python 2.7.3 en un macbook. Otros pueden obtener resultados totalmente diferentes.

De hecho, su código equivale a comparar los ID de objetos (es decir, su dirección física). Así que en lugar de su comparación es:

 >>> b = 'is it the space?' >>> a = 'is it the space?' >>> a is b False 

Tu puedes hacer:

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

Pero, tenga en cuenta que si a y b estuvieran directamente en la comparación funcionaría.

 >>> id('is it the space?') == id('is it the space?') True 

De hecho, en una expresión se comparte entre las mismas cadenas estáticas. Pero, a la escala del progtwig, solo se comparte para cadenas parecidas a palabras (por lo tanto, ni espacios ni puntuaciones).

No debe confiar en este comportamiento ya que no está documentado en ninguna parte y es un detalle de la implementación.

El operador ‘is’ compara el objeto real.

c is d también debe ser falso. Mi conjetura es que Python hace alguna optimización y en ese caso, es el mismo objeto.