python

python - ¿Por qué un espacio afecta la comparación de identidad de cadenas iguales?



(1)

El intérprete de Python almacena en caché algunas cadenas en función de ciertos criterios, la primera cadena abc se almacena en caché y se usa para ambos, pero la segunda no. Es lo mismo para pequeñas entradas de -5 a 256 .

Debido a que las cadenas están internadas / en caché, la asignación de a y b a "abc" hace que a y b apunten a los mismos objetos en la memoria, por lo que usar is , que comprueba si dos objetos son realmente el mismo objeto, devuelve True .

La segunda cadena abc abc no se almacena en la memoria caché, por lo que son dos objetos completamente diferentes en la memoria, por lo que la verificación de identidad que se usa is False . Esta vez a no es b . Ambos apuntan a diferentes objetos en la memoria.

In [43]: a = "abc" # python caches abc In [44]: b = "abc" # it reuses the object when assigning to b In [45]: id(a) Out[45]: 139806825858808 # same id''s, same object in memory In [46]: id(b) Out[46]: 139806825858808 In [47]: a = ''abc abc'' # not cached In [48]: id(a) Out[48]: 139806688800984 In [49]: b = ''abc abc'' In [50]: id(b) # different id''s different objects Out[50]: 139806688801208

El criterio para el almacenamiento en caché de cadenas es si la cadena solo tiene letras , guiones bajos y números en la cadena, por lo que en su caso el espacio no cumple con los criterios.

Al usar el intérprete, hay un caso en el que puede terminar apuntando al mismo objeto incluso cuando la cadena no cumple con los criterios anteriores, asignaciones múltiples.

In [51]: a,b = ''abc abc'',''abc abc'' In [52]: id(a) Out[52]: 139806688801768 In [53]: id(b) Out[53]: 139806688801768 In [54]: a is b Out[54]: True

Mirando la fuente codeobject.c para decidir los criterios que vemos, NAME_CHARS decide qué se puede internar:

#define NAME_CHARS / "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" /* all_name_chars(s): true iff all chars in s are valid NAME_CHARS */ static int all_name_chars(unsigned char *s) { static char ok_name_char[256]; static unsigned char *name_chars = (unsigned char *)NAME_CHARS; if (ok_name_char[*name_chars] == 0) { unsigned char *p; for (p = name_chars; *p; p++) ok_name_char[*p] = 1; } while (*s) { if (ok_name_char[*s++] == 0) return 0; } return 1; }

Siempre se compartirá una cadena de longitud 0 o 1, como podemos ver en la función PyString_FromStringAndSize en la fuente stringobject.c .

/* share short strings */ if (size == 0) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; nullstring = op; Py_INCREF(op); } else if (size == 1 && str != NULL) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; characters[*str & UCHAR_MAX] = op; Py_INCREF(op); } return (PyObject *) op; }

No está directamente relacionado con la pregunta, pero para aquellos interesados, PyCode_New también de la fuente codeobject.c muestra cómo se codeobject.c más cadenas al construir un objeto de código una vez que las cadenas cumplen los criterios en all_name_chars .

PyCodeObject * PyCode_New(int argcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *lnotab) { PyCodeObject *co; Py_ssize_t i; /* Check argument types */ if (argcount < 0 || nlocals < 0 || code == NULL || consts == NULL || !PyTuple_Check(consts) || names == NULL || !PyTuple_Check(names) || varnames == NULL || !PyTuple_Check(varnames) || freevars == NULL || !PyTuple_Check(freevars) || cellvars == NULL || !PyTuple_Check(cellvars) || name == NULL || !PyString_Check(name) || filename == NULL || !PyString_Check(filename) || lnotab == NULL || !PyString_Check(lnotab) || !PyObject_CheckReadBuffer(code)) { PyErr_BadInternalCall(); return NULL; } intern_strings(names); intern_strings(varnames); intern_strings(freevars); intern_strings(cellvars); /* Intern selected string constants */ for (i = PyTuple_Size(consts); --i >= 0; ) { PyObject *v = PyTuple_GetItem(consts, i); if (!PyString_Check(v)) continue; if (!all_name_chars((unsigned char *)PyString_AS_STRING(v))) continue; PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i)); }

Esta respuesta se basa en asignaciones simples que utilizan el intérprete cpython, en lo que respecta a la pasantía en relación con funciones o cualquier otra funcionalidad fuera de las asignaciones simples, que no se preguntó ni respondió.

Si alguien con una mayor comprensión del código c tiene algo que agregar, no dude en editarlo.

Aquí hay una explicación mucho más exhaustiva de toda la secuencia interna.

Esta pregunta ya tiene una respuesta aquí:

Me he dado cuenta de que agregar un espacio a cadenas idénticas hace que se comparen con el uso desigual, mientras que las versiones sin espacio se comparan igual.

a = ''abc'' b = ''abc'' a is b #outputs: True a = ''abc abc'' b = ''abc abc'' a is b #outputs: False

He leído esta pregunta sobre comparar cadenas con == y is . Creo que esta es una pregunta diferente porque el carácter de espacio está cambiando el comportamiento, no la longitud de la cadena. Ver:

a = ''abc'' b = ''abc'' a is b # True a = ''gfhfghssrtjyhgjdagtaerjkdhhgffdhfdah'' b = ''gfhfghssrtjyhgjdagtaerjkdhhgffdhfdah'' a is b # True

¿Por qué agregar un espacio a la cadena cambia el resultado de esta comparación?