关于python:为什么空格会影响相等字符串的身份比较?

Why does a space affect the identity comparison of equal strings?

本问题已经有最佳答案,请猛点这里访问。

我注意到,使用is向相同的字符串添加空格会使它们的比较不相等,而非空格版本的比较相等。

1
2
3
4
5
6
7
8
9
a = 'abc'
b = 'abc'
a is b
#outputs: True

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

我读过这个关于比较字符串与==is的问题。我认为这是一个不同的问题,因为空格字符改变了行为,而不是字符串的长度。见:

1
2
3
4
5
6
7
a = 'abc'
b = 'abc'
a is b # True

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

为什么在字符串中添加空格会更改此比较的结果?


python解释器根据某些条件缓存一些字符串,第一个abc字符串被缓存并用于这两个条件,但第二个则不缓存。从-5256的小整数也是如此。

由于字符串被截取/缓存,将ab分配给"abc"使ab指向内存中的同一对象,因此使用is检查两个对象是否实际上是同一对象,返回True

第二个字符串abc abc没有被缓存,因此它们在内存中是两个完全不同的对象,因此使用is进行身份检查时返回False。这次a不是b。它们都指向内存中的不同对象。

1
2
3
4
5
6
7
8
9
10
11
12
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

缓存字符串的条件是,如果字符串中只有字母、下划线和数字,那么在这种情况下,空间不符合条件。

使用解释器,有一种情况下,即使字符串不符合上述条件,也可以指向同一对象,即多次赋值。

1
2
3
4
5
6
7
8
9
10
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决定什么可以被拘留:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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的字符串将始终共享,正如我们在stringobject.c源中的PyString_FromStringAndSize函数中看到的那样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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中的条件时,在构建代码对象时如何更多的字符串被截留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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代码有更深入理解的人有什么需要添加的,请随意编辑。

这里有一个更彻底的解释,整个弦乐节间。