本文整理了许多字符串驻留的坑,部分整合自wtfpython英文版,并增加了大量的后续说明。
# example1:
>>> a ="wtf"
>>> b ="wtf"
>>> a is b
True
# example2:
>>> a ="wtf!"
>>> b ="wtf!"
>>> a is b
False
# example3:
>>> a, b ="wtf!","wtf!"
>>> a is b
True# 3.7 版本返回结果为 False.
# example4:
>>>'a'*20is'aaaaaaaaaaaaaaaaaaaa'
True
>>>'a'*21is'aaaaaaaaaaaaaaaaaaaaa'
False# 3.7 版本返回结果为 True
字符串的这些问题,像是在和你说 1 != 1 一样坑爹。
究其原因,其实是CPython在编译的时候会自动进行优化,在某些情况下它会尝试使用已经存在的不可变对象,而不是创建一个新的对象,而恰好,字符串就是不可变对象。这种使用已存在的不可变对象的行为被称为“驻留 ” 。
驻留的原本设计意图是用于节省内存的,但是确实有时候会坑到程序员。怎样判断自己的字符串会否被驻留呢?请看这份代码:
https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19
简单地来讲:
1.所有长度为0和1的字符串都会被驻留
2.字符串在编译时被实现的会被驻留(如'wtf'会被驻留,但是 ''.join(['w', 't', 'f']) 不会)
3.字符串中只包含ASCII下的字母、数字和下划线时会被驻留. 所以'wtf!'由于包含!不会被驻留。
我们的example1中,由于发生了驻留,所以a和b是同一个字符串对象。而example2中,由于没有发生字符串驻留,a="wtf!"和b="wtf!"实际上使用的不是同一个字符串对象,你可以使用id获得对象的唯一标志,你会发现它们的不同:
a和b都为wtf!时:
>>> a ="wtf!"
>>> b ="wtf!"
>>> a is b
False
>>> a == b
True
>>> id(a)
2272774097864
>>> id(b)
2272774097024
再来看看没有发生驻留时的情况,a和b都为wtf时:
# a和b都为wtf
>>> a ="wtf"
>>> b ="wtf"
>>> a is b
True
>>> a == b
True
>>> id(a)
2272774096744
>>> id(b)
2272774096744
明白了吧?如果你想从结果识别对象是否发生驻留,关键就看对象的唯一标志有没有被改变。
不过,如example3所示,当你在同一行中将a和b都设置为 wtf! 的时候,Python解释器会创建一个新的对象,然后同时引用第二个变量,这时候它两的唯一标志id就是一样的。(example3仅适用于python3.7以下,后面被改了)。
example4中,发生了常量折叠,这其实也是一种优化技术。编译时表达式 'a'*20 会被替换成 'aaaaaaaaaaaaaaaaaaaa' (不要数了,20个),不过只有长度小于20的字符串才会发生常量替换,这就是为什么 'a'*21并不等于 'aaaaaaaaaaaaaaaaaaaaa' (不要数了,21个) 。
好,感谢大家的阅读,今天的.....等等,你以为这就结束了吗?还有呢:
>>> a =10
>>> b =10
>>> a is b
True
>>> a =256
>>> b =256
>>> a is b
True
>>> a =257
>>> b =257
>>> a is b
False
这又是为啥啊?
请注意,Python中,对于整数对象,如果其值处于[-5,256]的闭区间内,则值相同的对象是同一个对象,否则为不同对象。我知道你想问,别问,问就是源码本身就这么写的。
(其实主要还是从性能方面考虑,-5到256这段数值被经常使用,因此干脆设为同一个对象重复使用,避免分配空间—赋予类别—赋予初始值等一系列操作)。
全部0条评论
快来发表一下你的评论吧 !