本文整理了许多字符串驻留的坑,部分整合自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实用宝典