连续几篇文章都在写 python 字符串,这出乎我的意料了。但是,有的问题,不写不行,特别是那种灵机一动想到的问题,最后你发现,很多人根本不懂却又误以为自己懂了。那就继续刨根问底,探究个明白吧。
在上一篇文章《你真的知道python的字符串怎么用吗?》里,我突发奇想,将字符串跟列表做了比较,然后发现字符串竟然没有复制的方法。当时没有细想,只说要搁置疑问。过后,有好学的小伙伴在后台留言,与我交流这个问题,给了我一些启发。为了彻底弄懂它,我继续查了不少资料,今天,就跟大家分享一下我发现的东西吧。
本文标题的问题分为两部分:(1)python 中是否支持复制字符串?(2)如果不支持,为什么不支持?
请读者花几分钟想一下,想清楚后,把你的答案记住,然后再往下看。
让我们做一个约定(自愿遵守):如果看到最后,你推翻了现在的答案,建立了新的认知,这说明我写的内容有用,那请你任意赞赏,或者将本文分享给其他使用 python 的小伙伴。
1. 什么是复制字符串?
首先,必须要大家对“复制”这个概念达成共识。复制,也叫拷贝,英文单词是 copy,具体意思是“将某事物通过某种方式制作成相同的一份或多份的行为”(释义来自维基百科)。复制的结果是,出现了多份极其相似但却相互独立的事物(副本),举例来说,你有一份文档 x,然后复制一份并重新命名为 y,这两者是相互独立的,若你删除其中一个,另一个不会一起被删除。
这个词用在 python 里,我们想表达的是同样的意思,即复制行为会产生新的独立对象,它与原始对象极其相似,但两者的生命周期没有直接的关联关系。下面先用列表来举例:
list1 = [1,2]
id(list1)
>>> 1981119454856
list2 = list1.copy()
print(list1 == list2)
>>> true
id(list2)
>>> 1981116983752
上例中,列表 list2 是 list1 的副本,两者字面量相等,但是内存地址(即 id )不相等,是两个相互独立的对象。如果字符串能够做到同样的效果,那我们就说,字符串可以被复制,否则,我们说字符串不可以被复制。
2. 怎样能复制字符串?
有了上面的概念和示例,请先思考,你会用什么方式复制字符串呢?(暂停,思考3分钟)
好了,先看看下面的几种方法:
s0 = python猫
s1 = s0
s2 = str(s0)
s3 = s0[:]
s4 = s0 + ''
s5 = '%s' % s0
s6 = s0 * 1
s7 = .join(s0)
import copy
s8 = copy.copy(s0)
你想到的复制方式是否在以上8种方式里呢?那么,如果把 s0 至 s8 的 id 打印出来,有哪些会跟 s0 不同呢?
答案是,它们的内存地址 id 完全相同,也就是说,一顿操作猛如虎,结果却始终只有一份字符串,根本没有复制出新的字符串!
python猫 的老读者看到这,会心一笑,这不就是因为字符串的 intern 机制嘛,短字符串在内存中只会存在一份,在《python中的“特权种族”是什么?》这篇文章里提到过的。
但请别开心得太早,你可以把 s0 改成一个超长的字符串,例如:
s0 = python猫是来自喵星的客人,它喜欢地球和人类,正在学习python,而且想借助python变成人,它的微信公众号也叫python猫,欢迎你关注哦,喵喵喵喵~~~
然后,再重复上面的操作。最终,你会发现,s0 到 s8 的 id 还是完全相同。
是不是吃惊了呢?新的 s0 明明已经超过 intern 机制的长度了,为什么不会产生新的字符串呢?
首先,请你相信,超出 intern 机制的字符串可以存在多份,即你可以创建出值完全相同的多个字符串对象,因为字符串对象在内存中并不一定是唯一的:
s9 = python猫是来自喵星的客人,它喜欢地球和人类,正在学习python,而且想借助python变成人,它的微信公众号也叫python猫,欢迎你关注哦,喵喵喵喵~~~
print(id(s0) == id(s9))
>>> false
上例表明,你可以创建出多个相同的字符串对象,但是这种方法与前面列举的8种不同,因为它是独立于 s0 的操作,并不是一种复制操作。从理论上讲,python 完全可以提供一个方法,达到复制出新的副本的结果。现在的问题恰恰就是:为什么允许存在多个相等的字符串对象,但是却无法通过复制的方式来创建呢?
3. 为什么不允许复制字符串?
我发现,不仅字符串不允许复制,元组也如此,事实上,还有 int 、float 也不支持复制。它们都是不可变对象,为什么不可变对象就不支持复制操作呢?
在查资料的时候,我发现网上很多文章对于“不可变对象”的认识存在误区,这些人不知道 intern 机制的存在,误以为字符串对象在内存只能有唯一一个,进而误以为不可变对象就是在内存中只有一份的对象。所以,这些文章很容易推断出错误的结论:因为字符串是不可变对象,所以字符串不支持复制。
事实上,不可变对象跟复制操作之间,并没有必然的强相关的关系。肯定是出于别的原因,设计者才给不可变对象加上这种限制,这个原因是什么呢?
在知乎上,有敏锐的同学提出了我的疑问“python中如何复制一个值或字符串?”,可惜只有4个回答,而且都没答到点上。stackoverflow上恰好也有一个问题“how can i copy a python string?”,同样没多少人注意到,只有5个回答,好在最高票答案提到了一个点,即这样可以加快字典的查找速度。
然而,他说的这个点并不靠谱。字典要求键值是可哈希对象,可是计算字符串的哈希值是根据字面值计算,所以对多个相等的字符串对象,其哈希值其实是一样的,对计算和查找根本无影响。
w1 = python猫是来自喵星的客人,它喜欢地球和人类,正在学习python,而且想借助python变成人,它的微信公众号也叫python猫,欢迎你关注哦,喵喵喵喵~~~
w2 = python猫是来自喵星的客人,它喜欢地球和人类,正在学习python,而且想借助python变成人,它的微信公众号也叫python猫,欢迎你关注哦,喵喵喵喵~~~
print(w1 == w2)
>>> true
print(id(w1) == id(w2))
>>> false
print(hash(w1) == hash(w2))
>>> true
继续查资料,终于在《流畅的python》找到了明确的解释:
这些细节是 cpython 核心开发者走的捷径和做的优化措施,对这门语言的用户而言无需了解,而且那些细节对其他 python 实现可能没用,cpython 未来的版本可能也不会用。
这本《流畅的python》是进阶首选书目之一,我曾读过部分章节,没想到在一个不起眼的小节里,作者 “惊讶地发现” 元组的不可复制性,在此之前,他还自以为“对元组无所不知”,哈哈哈。
虽然,我早猜测到原因是节省内存和提高速度,但看到这个明确的解释,知道这只是cpython 解释器的“善意的谎言”,而且在未来版本可能不会用,我感到特别意外。
它证实了我的猜测,同时,也提供了超预期的信息:其它 python 解释器可能支持复制不可变对象,目前 cpython 算是一种妥协,在未来可能会恢复不可变对象的复制操作呢!
回到文章开头的两个问题,我们得到的答案是:python 本身并不限制字符串的复制操作,只是当前版本的 cpython 做了优化,才导致出现这种“善意的谎言”,它这么做的原因为了对 intern 机制做补充,设法使全部字符串对象在内存都只有一份,以达到节省内存的效果。
cpython 是用 c 语言实现的 python 解释器,是官方的、使用最广泛的解释器。除了它,还有用 java 实现的 jython 解释器、用 .net 实现的 ironpython 解释器、用 python 实现的 pypy 解释器,等等。其它解释器都是怎么应对字符串的复制操作的呢?唉,学无止境,本人才疏学浅没有涉猎,还是先搁置疑问吧。
这里,我就想提一个题外话,python 最最最广为人诟病的就是 gil(全局解释器锁),这导致它不支持真正意义的多线程,成为很多人指责 python 慢的元凶。但是,这个问题是 cpython 解释器带来的,而像 jython 解释器就不存在这个问题。
好了,就此打住吧。你是否还记得在文章开头时想到的答案呢?是否改变了最初的想法呢?欢迎关注公众号 python猫 ,来跟我交流,一起来学习 python ,做个合格的 pythonista。
用LED灯替代汽车车灯的七大好处
一文读懂51单片机的RAM分区
苹果推送第三个iOS/iPadOS 14.5开发者测试版
我国成功打破西方的垄断,攻克9nm芯片技术难关
新能源电池种类
什么是复制字符串?Python如何复制字符串
Frost Sullivan:“Xilinx引领自动驾驶技术的未来”
诺基亚8本月16发布,真机曝光:你要的情怀配置都有,就是外观长这样要买4000?
美国造车新势力盯上中国市场,“水土不服”还是复现“特斯拉式”成功?
研究人员发现增强电化学微传感器性能的新方法,其竟还能解释物理学科
简述半导体的导电机理
Micrium全家桶之uC-CRC: 0x02 CRC
华硕发布首款Wi-Fi6桌面无线网卡 最高速度超3Gbps
Java将如何促进主流区块链的采用
PLC在馈电开关控制系统中的应用和分析
华为愿意将5G技术独家授权给美国公司
超级计算机“星云”下月在深圳开通运行
一款自制简易示波器设计
增强网络安全意识才是网络贷款和非法催债的解决方法
怎样才能将人工智能用得好