连着好几篇写Python字符串的文章,出乎我的意料。但是,有些问题不是不写就可以解决的,尤其是那种心血来潮想起来的问题。最后你发现很多人根本不懂却误以为自己懂了。然后继续追根究底,一探究竟就明白了。在上一篇《你真的知道Python的字符串怎么用吗?》中,心血来潮,将字符串与列表进行了对比,发现没有办法复制字符串。我当时也没多想,只是说我会放下疑虑。后来有好学的朋友在后台留言和我交流这个问题,给了我一些启发。为了彻底了解它,我继续查了很多资料。今天,我将与您分享我的发现。本文标题中的问题分为两部分:(1)Python是否支持复制字符串?(2)如果不支持,为什么不支持?请读者花几分钟思考一下,思考清楚后,记住你的答案,然后继续阅读。许个诺言(自愿遵守):如果你看到最后,你推翻了现在的答案,建立了新的认知,说明我写的内容有用,那么请随意欣赏,或者把这篇文章分享给其他网友Python的小伙伴。1、什么是复制字符串?首先,大家要对“复制”这个概念达成共识。复制,也叫拷贝,英文单词copy,具体意思是“以某种方式将某物制作一份或多份的行为”(定义来自维基百科)。复制的结果是有很多非常相似但独立的东西(副本)。比如你有一个文档X,然后复制重命名为Y,两者是相互独立的。如果您删除其中一个,另一个将不会一起删除。当这个词在Python中使用时,我们指的是同一件事,即复制行为会产生一个新的独立对象,该对象与原始对象非常相似,但其生命周期并不直接相关。我们先以列表为例:list1=[1,2]id(list1)>>>1981119454856list2=list1.copy()print(list1==list2)>>>Trueid(list2)>>>1981116983752In上面的例子,列表list2是list1的副本,两者字面量相等,但内存地址(即id)不相等,是两个独立的对象。如果字符串能达到同样的效果,那么我们就说这个字符串可以被复制,否则我们就说这个字符串不能被复制。2.如何复制一个字符串?有了上面的概念和例子,请先想一想,你会如何复制一个字符串?(停顿,思考3分钟)好吧,我们来看下面的方法:s0="Pythoncat"s1=s0s2=str(s0)s3=s0[:]s4=s0+''s5='%s'%s0s6=s0*1s7="".join(s0)importcopies8=copy.copy(s0)以上8种复制方式你想到了吗?那么,如果打印出s0到s8的id,哪些会和s0不一样呢?答案是它们的内存地址id完全一样,也就是说操作猛如虎,结果却总是只有一份字符串,根本没有复制新的字符串!Python猫老读者看到这里会心一笑。这不是因为字符串的Intern机制吗?shortstrings在内存中只会有一份,在《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机制的存在,误认为内存中只能有一个string对象,进而误认为immutableobjects是内存中只有一份的对象。所以,这些文章很容易得出错误的结论:因为字符串是不可变对象,所以字符串不支持复制。事实上,不可变对象和复制操作之间没有必然的强关系。一定是出于其他原因,设计者对不可变对象施加了这种限制。是什么原因?在知乎上,有热心的同学提出了我的问题“如何在Python中复制一个值或字符串?”不幸的是,只有4个答案,没有一个回答到重点。也恰好有一个问题“如何复制Python字符串?”在计算器上。没有多少人注意到它,只有5个答案。好在得票最高的答案提到了一点,就是可以加快查字典的速度。不过,他说的这一点并不靠谱。字典要求key值是一个hashable对象,但是字符串的hash值是根据字面值计算的,所以对于多个相等的string对象,hash值其实是一样的,对计算和查找没有影响.w1="蟒星猫是喵星人的客人,喜欢地球,喜欢人类,正在学习Python,想借助Python成为人类。它的微信公众号也叫蟒猫,欢迎大家付费注意,喵喵喵~~~"w2="蟒蛇猫是喵星人的客人,喜欢地球,喜欢人类,正在学习Python,想用Python变成人。它的微信公众号也是叫Python猫,欢迎关注喵喵喵~~~"print(w1==w2)>>>Trueprint(id(w1)==id(w2))>>>Falseprint(hash(w1)==hash(w2))>>>>True继续查找资料,终于在《流畅的Python》中找到了明确的解释:这些细节是CPython核心开发者采取的捷径和优化措施,而这个的使用者语言不需要理解,那些细节也不需要,其他Python的实现可能行不通,未来版本的CPython也可能行不通。这个《流畅的Python》是进修的首选书目之一。看过一些章节,没想到在一个不起眼的小节,作者“惊奇”地发现了元祖的不可复制性。在此之前,他还以为“元祖无所不知”,哈哈哈。虽然,我猜想是为了节省内存和提高速度,但看到这个明明白白的解释还是很惊讶,知道这只是CPython解释器的一个“善意的谎言”,以后的版本可能不会使用。它证实了我的猜测,同时也提供了意想不到的信息:其他Python解释器可能支持复制不可变对象。目前CPython算是妥协了,以后可能会恢复不可变对象的复制操作!回到文章开头的两个问题,我们得到的答案是:Python本身并没有限制对字符串的复制操作,而是当前版本的CPython进行了优化,才导致了这个“善意的谎言”。原因是为了补充Intern机制,尽量让所有的string对象在内存中只有一份,达到节省内存的效果。CPython是用C语言实现的Python解释器,是官方最广泛使用的解释器。除此之外,还有用Java实现的Jython解释器,用.NET实现的IronPython解释器,用Python实现的PyPy解释器等等。其他解释器如何处理字符串复制操作?唉,学无止境。本人知之甚少,没有涉足,暂且放下疑惑。在这里,我想提一个题外话。Python最受诟病的就是GIL(GlobalInterpreterLock),这导致它不支持真正的多线程,成为很多人指责Python慢??的罪魁祸首。不过这个问题是CPython解释器带来的,Jython解释器没有这个问题。好吧,让我们到此为止。还记得文章开头想到的答案吗?改变初衷了吗?欢迎关注公众号Python猫,来和我交流,一起学习Python,做一个合格的Pythonista。参考学习:《流畅的Python》https://www.zhihu.com/questio...https://dwz.cn/4o0WXy8G最后福利时刻:本《流畅的Python》(蟒猫)由清华大学出版社赞助,将抽奖送出两本新书《深入浅出Python机器学习》,截止至11月29日18:18,点击此链接即刻参与。本文首发于微信公众号【Python猫】,后台回复“爱学习”,送20+精选电子书。
