CPython中的字符串身份比较

4 投票
5 回答
575 浏览
提问于 2025-04-16 10:57

我最近发现了一个可能在生产系统中存在的bug,问题是用身份运算符比较了两个字符串,比如:

if val[2] is not 's':

我想这通常还是能正常工作的,因为据我所知,CPython会把短小的不可变字符串存储在同一个地方。我已经把它改成了!=,但我需要确认之前通过这段代码的数据是否正确,所以我想知道这段代码是否总是有效,还是说只是偶尔有效。

据我了解,Python的版本一直是2.6.6,而上面的代码似乎是唯一使用了is运算符的地方。

有没有人知道这一行代码是否会始终按照程序员的意图工作?

补充:因为这个问题无疑对未来的读者来说非常具体且不太有帮助,我想问一个不同的问题:

我应该去哪里确认Python实现的行为,以确保绝对准确?CPython的源代码中的优化是否容易理解?有什么建议吗?

5 个回答

3

正如大家已经提到的,对于在Python(或者说CPython)中创建的字符串,这个说法通常是正确的。但如果你使用了C扩展,那就不一定了。

这里有个简单的反例:

import numpy as np

x = 's'
y = np.array(['s'], dtype='|S1')

print x
print y[0]

print 'x is y[0] -->', x is y[0]
print 'x == y[0] -->', x == y[0]

这个会得到:

s
s
x is y[0] --> False
x == y[0] --> True

当然,如果你从来没有使用过任何C扩展,那你可能是安全的……不过我不建议你完全依赖这个……

补充一下:如果数据经过了序列化(pickled)或者用struct以任何方式打包,那这个说法就不成立了。

比如:

import pickle
x = 's'
pickle.dump(x, file('test', 'w'))
y = pickle.load(file('test', 'r'))

print x is y
print x == y

另外(为了更清楚,我们用不同的字母,因为格式化字符串需要用"s"):

import struct
x = 'a'
y = struct.pack('s', x)

print x is y
print x == y
3

你绝对不应该在只想比较两个对象是否相等的时候使用 isis not 这个操作符。

虽然在 Python 中,字符串是不可变的,所以它不会创建一个内容和已有字符串完全相同的新字符串(这也就意味着相等和身份是一样的),但我不建议你依赖这个,特别是考虑到有很多不同的 Python 实现。

3

你可以查看CPython 2.6.x的代码:http://svn.python.org/projects/python/branches/release26-maint/Objects/stringobject.c

看起来一字符的字符串会被特别处理,每个不同的字符只会存在一次,所以你的代码是安全的。这里有一些关键代码(摘录):

static PyStringObject *characters[UCHAR_MAX + 1];

PyObject *
PyString_FromStringAndSize(const char *str, Py_ssize_t size)
{
    register PyStringObject *op;
    if (size == 1 && str != NULL &&
        (op = characters[*str & UCHAR_MAX]) != NULL)
    {
        Py_INCREF(op);
        return (PyObject *)op;
    }

...

撰写回答