上一篇《Python是否支持复制字符串呢?》发表了一段时间,@发条橙爽在后台留言,指出错误。我吓了一跳,立马去验证,结果果然不对,而且是在一个我完全没想到的地方!一开始以为只是一个疏漏,后来仔细想想,发现不简单,遇到了一个看不懂的问题。所以,这篇文章又得说说字符串了。和往常一样,先总结一下本文的内容:(1)join()方法不仅拼接字符串速度快,而且是复制字符串最通用、最有效的方法。(2)Intern机制(字符串保留)不是万能的是的,本文探讨它的弱点1.join()方法不仅仅是拼接先把问题简化一下:ss0='hi'ss1='h'+'i's2=''.join(ss0)print(ss0==ss1==ss2)>>>Trueprint(id(ss0)==id(ss1))>>>Trueprint(id(ss0)==id(ss2))>>>上面代码错误,奇怪的是ss2是一个独立的对象!按照我想当然的初步认知,相信会被Intern机制处理,所以不会占用独立内存。上一篇快写完的时候,突然想到join的方法,所以没有验证就临时加上了,结果出了意外。根据之前《特权种族》一文的总结,我对字符串Intern机制有这样的理解:在Python中,字符串使用Intern机制共享内存地址,长度不超过20,并且只包括下划线,数字,和letters字符串将是intern;当涉及到字符串拼接时,编译时的优化结果会与运行时的计算结果不同。为什么join方法在拼接字符串时不受Intern机制的影响?回头看那篇文章,发现编译时和运行时可能是有区别的!#编译对字符串拼接的影响s1="hell"s2="hello""hell"+"o"iss2>>>Trues1+"o"iss2>>>False#"hell"+"o"in编译时就变成了"hello",#和s1+"o"是在运行时拼接的,因为s1是一个变量,所以没有被intern测试过,见:#代码加上ss3=''.join('hi')print(id(ss0)==id(ss3))>>>Falsess3还是一个独立的对象,这种写法是不是运行时还在拼接?那么如何判断某种写法在编译时有效还是运行时有效呢?继续实验:s0="Pythoncat"importcopies1=copy.copy(s0)s2=copy.copy("Pythoncat")print(id(s0)==id(s1))>>>Trueprint(id(s0)==id(s2))>>>False好像不能通过是否显式传递值来判断。那我们只能从join方法的实现原理说起。被交流群里的朋友提醒,可以去PythonTutor官网看可视化执行过程。然而,不幸的是,没有底层机制。找了分析CPython源码的资料(包括上一期推荐书栏的《Python源码剖析》)来学习,但是这些资料只是比较了join()方法和+拼接在原理和内存占用上的区别method,并且不提为什么Intern机制对前者无效,对后者有效。现象已经出现了,我只能暂时说明join方法不会被Intern机制控制,它具有独占内存的“特权”。也就是说,有多种方法可以复制字符串!上一篇《Python是否支持复制字符串呢?》没有发现这一点,最后得出了错误的结论!由于这种特殊情况,我要修正上一篇文章的结论:Python本身不限制字符串的复制操作。CPython解释器添加了一些技巧来尝试使字符串对象在内存中只有一个字符串对象。但是,仍然有一种方法可以高效地复制字符串,那就是join()方法。2.Intern机制的无效性join()方法的神奇使用,让我不得不改变对Intern机制的认识。本节将带你重新学习Intern机制。所谓Intern机制,即stringinterning,维护一个字符串常量池(stringinternpool),从而尽量只保存唯一的字符串对象,从而达到高效处理字符串和节省内存的目的。Python在创建一个新的字符串对象后,首先比较常量池中是否有相同的对象(interned),如果有,则将指针指向已有对象并减少新对象的指针。由于新对象没有引用计数,会被垃圾回收机制回收,释放内存。Intern机制不会减少新对象的创建和销毁,但最终会节省内存。这种机制的另一个优点是它几乎不需要时间来比较相同的内部字符串。实验数据如下(来源:http://t.cn/ELu9n7R):Intern机制的大致原理很好理解,但是结果也会受到CPython解释器其他编译运行机制的影响。字符串对象受这些机制的影响。综合作用。事实上,只处理“看起来像”Python标识符的字符串。源代码StringObject.h的注释写道:/......这通常仅限于“看起来像”Python标识符的字符串,尽管内置的intern()可用于强制实习任何字符串...。../Theinteractionofthesemechanisms,inadvertentlybroughtalotofconfusion:#lengthexceeds20,notinternedVSintern'a'*21is'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'is'aaaaaaaaaaaaaaaaaaa'>>>True#lengthisnotMorethan20,notinternVSinterns='a's*5is'aaaaa'>>>False'a'*5is'aaaaa'>>>True#join方法,notinternVSintern''.join('hi')is'hi'>>>False''.join('h')是'h'>>>True#特殊符号,不是internVSby"intern"'python!'是“蟒蛇!”>>>Falsea,b='python!','python!'aisb>>>True这些现象当然可以合理解释,但由于不同机制的混合作用,很容易造成误解。比如第一个例子,很多介绍Intern机制的文章都认为Intern机制在比较'a'*21的id后,只对长度不超过20的字符串有效。当字符串的id为仍然相等,这个结论就错了。当加入常量折叠(Constantfolding)机制时,解释了长度不超过20的字符串会被合并的现象。但是在CPython的源代码中,只有长度不超过1字节的字符串才会被intern。为什么长度超标?只有加入CPython的编译优化机制才能解释清楚。所以,这两个看似intern的字符串,实际上可能不是Intern机制的结果,而是其他机制的结果。同样,两个看似可嵌入的字符串实际上可能会被其他机制以类似的方式处理。这些都增加了理解Intern机制的难度。就我在上一篇文章中关注的“复制字符串”这个话题而言,只有在Intern机制和这些其他机制失败的情况下才能复制字符串。join方法似乎是迄今为止最通用的。3.学习的方法论总而言之,我之所以能够改正上一篇文章的错误,是因为重新学习了join方法的妙用和Intern机制的exception。在这个过程中,我获得了新的知识,获得了思考和学习的乐趣。《超人》电影中有一句名言,今年上映的《头号玩家》也出现了:有些人从《战争与和平》看到的只是一个普通的冒险故事,而另一些人则可以通过阅读口香糖包装纸上的成分表来解锁宇宙的奥秘。我读到的是一种敏锐的思辨思维,一种勤奋的态度,一种以小见大的方法。作为一个天赋低下的人,受此启发,我会不断地问一些看似毫无意义的问题(“如何删除一个字符串”,“如何复制一个字符串”……),一点一点地学习Python,一点一点地学习Python一点点。我的理解方式。同时也希望能给我的读者带来一些收获。附言。很多人都在期待《蟒猫》系列,别着急,让猫多睡几天,等它醒了,我会为大家催促的!字符串系列文章:详解Python拼接字符串的七种方式你真的了解Python字符串是什么吗?你真的知道如何使用Python字符串吗?Python支持复制字符串吗?Python猫系列:用Python,我可以命名所有的猫Python对象的身份神话:从所有公民到一切都很重要----------------本文为原创,已发表已转载首发微信公众号【Python猫】,后台回复“爱学习”,免费领取20+精选电子书。
