共计 2952 个字符,预计需要花费 8 分钟才能阅读完成。
本文题目的问题分为两局部:(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 完全相同,也就是说,一顿操作猛如虎,后果却始终只有一份字符串,基本没有复制出新的字符串!
会心一笑,这不就是因为字符串的 Intern 机制嘛,短字符串在内存中只会存在一份
但请别开心得太早,你能够把 s0 改成一个超长的字符串,例如:
s0 = “Python 编程学习圈,正在学习 Python,它的微信公众号也叫 Python 编程学习圈,欢送你关注哦 ”
而后,再反复下面的操作。最终,你会发现,s0 到 s8 的 id 还是完全相同。
是不是吃惊了呢?新的 s0 明明曾经超过 Intern 机制的长度了,为什么不会产生新的字符串呢?
首先,请你置信,超出 Intern 机制的字符串能够存在多份,即你能够创立出值完全相同的多个字符串对象,因为字符串对象在内存中并不一定是惟一的:
s9 = "Python 编程学习圈,正在学习 Python,它的微信公众号也叫 Python 编程学习圈,欢送你关注哦"
print(id(s0) == id(s9))
>>> False
上例表明,你能够创立出多个雷同的字符串对象,然而这种办法与后面列举的 8 种不同,因为它是独立于 s0 的操作,并不是一种复制操作。从实践上讲,Python 齐全能够提供一个办法,达到复制出新的正本的后果。当初的问题恰好就是:为什么容许存在多个相等的字符串对象,然而却无奈通过复制的形式来创立呢?
**
- 为什么不容许复制字符串?**
我发现,不仅字符串不容许复制,元组也如此,事实上,还有 int、float 也不反对复制。它们都是不可变对象,为什么不可变对象就不反对复制操作呢?
在查资料的时候,我发现网上很多文章对于“不可变对象”的意识存在误区,这些人不晓得 Intern 机制的存在,误以为字符串对象在内存只能有惟一一个,进而误以为不可变对象就是在内存中只有一份的对象。所以,这些文章很容易推断出谬误的论断:因为字符串是不可变对象,所以字符串不反对复制。
事实上,不可变对象跟复制操作之间,并没有必然的强相干的关系。必定是出于别的起因,设计者才给不可变对象加上这种限度,这个起因是什么呢?
在知乎上,有敏锐的同学提出了我的疑难“Python 中如何复制一个值或字符串?”,惋惜只有 4 个答复,而且都没答到点上。Stackoverflow 上恰好也有一个问题“How can I copy a Python string?”,同样没多少人留神到,只有 5 个答复,好在最高票答案提到了一个点,即这样能够放慢字典的查找速度。
然而,他说的这个点并不靠谱。字典要求键值是可哈希对象,可是计算字符串的哈希值是依据字面值计算,所以对多个相等的字符串对象,其哈希值其实是一样的,对计算和查找基本无影响。
w1 = "Python 编程学习圈,正在学习 Python,它的微信公众号也叫 Python 编程学习圈,欢送你关注哦"
w2 = "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 编程学习圈 ,发送“J”即可收费获取,每日干货分享