CPython中的字符串身份比较
我最近发现了一个可能在生产系统中存在的bug,问题是用身份运算符比较了两个字符串,比如:
if val[2] is not 's':
我想这通常还是能正常工作的,因为据我所知,CPython会把短小的不可变字符串存储在同一个地方。我已经把它改成了!=
,但我需要确认之前通过这段代码的数据是否正确,所以我想知道这段代码是否总是有效,还是说只是偶尔有效。
据我了解,Python的版本一直是2.6.6,而上面的代码似乎是唯一使用了is
运算符的地方。
有没有人知道这一行代码是否会始终按照程序员的意图工作?
补充:因为这个问题无疑对未来的读者来说非常具体且不太有帮助,我想问一个不同的问题:
我应该去哪里确认Python实现的行为,以确保绝对准确?CPython的源代码中的优化是否容易理解?有什么建议吗?
5 个回答
正如大家已经提到的,对于在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
你绝对不应该在只想比较两个对象是否相等的时候使用 is
或 is not
这个操作符。
虽然在 Python 中,字符串是不可变的,所以它不会创建一个内容和已有字符串完全相同的新字符串(这也就意味着相等和身份是一样的),但我不建议你依赖这个,特别是考虑到有很多不同的 Python 实现。
你可以查看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;
}
...