Python 小坑之字符串驻留

本文整理了许多字符串驻留的坑,部分整合自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' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is '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这段数值被经常使用,因此干脆设为同一个对象重复使用,避免分配空间—赋予类别—赋予初始值等一系列操作)。

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注