Почему пространство влияет на сравнение идентичных строк?

Я заметил, что добавление пробела в идентичные строки заставляет их сравнивать неравномерно с помощью is, в то время как непространственные версии сравниваются равными.

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

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

Я прочитал этот вопрос о сравнении строк с == и is. Я думаю, что это другой вопрос, потому что космический символ меняет поведение, а не длину строки. См:

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

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

Почему добавление пробела в строку меняет результат этого сравнения?

Ответ 1

Интерпретатор python кэширует некоторые строки на основе определенных критериев, первая строка abc кэшируется и используется для обоих, но вторая - нет. То же самое для небольших ints от -5 до 256.

Поскольку строки интернированы/кэшированы, присваивая a и b - "abc", заставляет a и b указывать на одни и те же объекты в памяти, поэтому используя is, который проверяет, действительно ли два объекта тот же объект возвращает True.

Вторая строка abc abc не кэшируется, поэтому они представляют собой два совершенно разных объекта в памяти, поэтому проверка идентификации с помощью is возвращает False. На этот раз a не b. Они оба указывают на разные объекты в памяти.

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 different objects
Out[50]: 139806688801208

Критерии для кеширования строк: если строка содержит только буквы, символы подчеркивания и числа в строке, поэтому в вашем случае пространство не соответствует критериям.

Использование интерпретатора - это один случай, когда вы можете в конечном итоге указывать на один и тот же объект, даже если строка не соответствует указанным выше критериям, несколько назначений.

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

В поисках источника codeobject.c для определения критериев, которые мы видим, NAME_CHARS решает, что может быть интернировано:

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

Строка длиной 0 или 1 всегда будет разделяться, как мы можем видеть в функции PyString_FromStringAndSize в источнике 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;
}

Не связанный напрямую с вопросом, но для тех, кого интересует PyCode_New, также из источника codeobject.c показывает, как больше строк интернировано при создании кода-объекта после того, как строки соответствуют критериям в 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));
    }

Этот ответ основан на простых присваиваниях с использованием интерпретатора cpython, поскольку касается интернирования по отношению к функциям или любой другой функциональности за пределами простых назначений, которые не задавались и не отвечали.

Если кто-либо, у кого больше понимает код c, есть что добавить, не стесняйтесь редактировать.

Ниже представлено более подробное объяснение для всего перевода строк.