摘要:機制失效的情況方法的神奇用處使我不得不改變對機制的認識,本小節(jié)就帶大家重新學習一下機制吧。目前看來,方法最具通用性。學習的方法論總而言之,因為重新學習方法的神奇用處與機制的例外情況,我得以修正上篇文章的錯誤。
上篇文章《Python是否支持復制字符串呢?》剛發(fā)出一會,@發(fā)條橙 同學就在后臺留言,指出了一處錯誤。我一驚,馬上去驗證,竟然真的錯了,而且在完全沒意料到的地方!我開始以為只是疏漏,一細想,發(fā)現(xiàn)不簡單,遇到了百思不得其解的問題了。所以,這篇文章還得再聊聊字符串。
照例先總結下本文內(nèi)容:(1)join() 方法除了在拼接字符串時速度較快,它還是目前看來最通用有效的復制字符串的方法 (2)Intern 機制(字符串滯留)并非萬能的,本文探索一下它的軟肋有哪些
1. join()方法不止是拼接我先把那個問題化簡一下吧:
ss0 = "hi" ss1 = "h" + "i" ss2 = "".join(ss0) print(ss0 == ss1 == ss2) >>> True print(id(ss0) == id(ss1)) >>> True print(id(ss0) == id(ss2)) >>> False
上面代碼中,奇怪的地方就在于 ss2 竟然是一個獨立的對象!按照最初想當然的認知,我認定它會被 Intern 機制處理掉,所以是不會占用獨立內(nèi)存的。上篇文章快寫完的時候,我突然想到 join 方法,所以沒做驗證就臨時加進去,導致了意外的發(fā)生。
按照之前在“特權種族”那篇文章的總結,我對字符串 Intern 機制有這樣的認識:
Python中,字符串使用Intern機制實現(xiàn)內(nèi)存地址共用,長度不超過20,且僅包括下劃線、數(shù)字、字母的字符串才會被intern;涉及字符串拼接時,編譯期優(yōu)化結果會與運行期計算結果不同。
為什么 join 方法拼接字符串時,可以不受 Intern 機制作用呢?
回看那篇文章,發(fā)現(xiàn)可能存在編譯期與運行期的差別!
# 編譯對字符串拼接的影響 s1 = "hell" s2 = "hello" "hell" + "o" is s2 >>>True s1 + "o" is s2 >>>False # "hell" + "o"在編譯時變成了"hello", # 而s1+"o"因為s1是一個變量,在運行時才拼接,所以沒有被intern
實驗一下,看看:
# 代碼加上 ss3 = "".join("hi") print(id(ss0) == id(ss3)) >>> False
ss3 仍然是獨立對象,難道這種寫法還是在運行期時拼接?那怎么判斷某種寫法在編譯期還是在運行期起作用呢?繼續(xù)實驗:
s0 = "Python貓" import copy s1 = copy.copy(s0) s2 = copy.copy("Python貓") print(id(s0) == id(s1)) >>> True print(id(s0) == id(s2)) >>> False
看來,不能通過是否顯性傳值來判斷。
那就只能從 join 方法的實現(xiàn)原理入手查看了。經(jīng)某交流群的小伙伴提醒,可以去 Python Tutor 網(wǎng)站,看看可視化執(zhí)行過程。但是,很遺憾,也沒看出什么底層機制。
我找了分析 CPython 源碼的資料(含上期薦書欄目的《Python源碼剖析》)來學習,但是,這些資料只比較 join() 方法與 + 號拼接法在原理與使用內(nèi)存上的差異,并沒提及為何 Intern 機制對前者會失效,而對后者卻是生效的。
現(xiàn)象已經(jīng)產(chǎn)生,我只能暫時解釋說,join 方法會不受 Intern 機制控制,它有獨享內(nèi)存的“特權”。
那就是說,其實有復制字符串的方法!上篇《Python是否支持復制字符串呢?》由于沒有發(fā)現(xiàn)這點,最后得出了錯誤的結論!
由于這個特例,我要修改上篇文章的結論了:Python 本身并不限制字符串的復制操作,CPython 解釋器出于優(yōu)化性能的考慮,加入了一些小把戲,試圖使字符串對象在內(nèi)存中只有一份,盡管如此,仍存在有效復制字符串的方法,那就是 join() 方法。
2. Intern 機制失效的情況join() 方法的神奇用處使我不得不改變對 Intern 機制的認識,本小節(jié)就帶大家重新學習一下 Intern 機制吧。
所謂 Intern 機制,即字符串滯留(string interning),它通過維護一個字符串常量池(string intern pool),從而試圖只保存唯一的字符串對象,達到既高效又節(jié)省內(nèi)存地處理字符串的目的。在創(chuàng)建一個新的字符串對象后,Python 先比較常量池中是否有相同的對象(interned),有的話則將指針指向已有對象,并減少新對象的指針,新對象由于沒有引用計數(shù),就會被垃圾回收機制回收掉,釋放出內(nèi)存。
Intern 機制不會減少新對象的創(chuàng)建與銷毀,但最終會節(jié)省出內(nèi)存。這種機制還有另一個好處,即被 Interned 的相同字符串作比較時,幾乎不花時間。實驗數(shù)據(jù)如下(資料來源:http://t.cn/ELu9n7R):
Intern 機制的大致原理很好理解,然而影響結果的還有 CPython 解釋器的其它編譯及運行機制,字符串對象受到這些機制的共同影響。實際上,只有那些“看起來像” Python 標識符的字符串才會被處理。源代碼StringObject.h的注釋中寫道:
/ … … This is generally restricted to strings that “l(fā)ooklike” Python identifiers, although the intern() builtin can be used to force interning of any string … … /
這些機制的相互作用,不經(jīng)意間帶來了不少混亂的現(xiàn)象:
# 長度超過20,不被intern VS 被intern "a" * 21 is "aaaaaaaaaaaaaaaaaaaaa" >>> False "aaaaaaaaaaaaaaaaaaaaa" is "aaaaaaaaaaaaaaaaaaaaa" >>> True # 長度不超過20,不被intern VS 被intern s = "a" s * 5 is "aaaaa" >>> False "a" * 5 is "aaaaa" >>> True # join方法,不被intern VS 被intern "".join("hi") is "hi" >>> False "".join("h") is "h" >>> True # 特殊符號,不被intern VS 被"intern" "python!" is "python!" >>> False a, b = "python!", "python!" a is b >>> True
這些現(xiàn)象當然都能被合理解釋,然而由于不同機制的混合作用,就很容易造成誤會。比如第一個例子,很多介紹 Intern 機制的文章在比較出 "a" * 21 的id有變化后,就認為 Intern 機制只對長度不超過20的字符串生效,可是,當看到長度超過20的字符串的id還相等時,這個結論就變錯誤了。當加入常量合并(Constant folding) 的機制后,長度不超過20的字符串會被合并的現(xiàn)象才得到解釋??墒牵?CPython 的源碼中,只有長度不超過1字節(jié)的字符串才會被 intern ,為何長度超標的情況也出現(xiàn)了呢? 再加入 CPython 的編譯優(yōu)化機制,才能解釋。
所以,看似被 intern 的兩個字符串,實際可能不是 Intern 機制的結果,而是其它機制的結果。同樣地,看似不能被 intern 的兩個字符串,實際可能被其它機制以類似方式處理了。
如此種種,便提高了理解 Intern 機制的難度。
就我在上篇文章中所關心的“復制字符串”話題而言,只有當 Intern 機制與其它這些機制統(tǒng)統(tǒng)失效時,才能做到復制字符串。目前看來,join 方法最具通用性。
3. 學習的方法論總而言之,因為重新學習 join 方法的神奇用處與 Intern 機制的例外情況,我得以修正上篇文章的錯誤。在此過程中,我得到了新的知識,以及思考學習的樂趣。
《超人》電影中有一句著名的臺詞,在今年上映的《頭號玩家》中也出現(xiàn)了:
有的人從《戰(zhàn)爭與和平》里看到的只是一個普通的冒險故事,有的人則能通過閱讀口香糖包裝紙上的成分表來解開宇宙的奧秘。
我讀到的是一種敏銳思辨的思想、孜孜求索的態(tài)度和以小窺大的方法。作為一個低天賦的人,受此鼓舞,我會繼續(xù)追問那些看似沒意義的問題(“如何刪除字符串”、“如何復制字符串”...),一點一點地學習 Python ,以我的方式理解它。同時,希望能給我的讀者們帶來一些收獲。
PS.不少人在期待 “Python貓” 系列,別急哈,讓那只貓再睡幾天,等它醒來,我替大家催它!
字符串系列文章:
詳解Python拼接字符串的七種方式
你真的知道Python的字符串是什么嗎?
你真的知道Python的字符串怎么用嗎?
Python是否支持復制字符串呢?
Python貓系列:
有了Python,我能叫出所有貓的名字
Python對象的身份迷思:從全體公民到萬物皆數(shù)
-----------------
本文原創(chuàng)并首發(fā)于微信公眾號【Python貓】,后臺回復“愛學習”,免費獲得20+本精選電子書。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42713.html
摘要:機制失效的情況方法的神奇用處使我不得不改變對機制的認識,本小節(jié)就帶大家重新學習一下機制吧。目前看來,方法最具通用性。學習的方法論總而言之,因為重新學習方法的神奇用處與機制的例外情況,我得以修正上篇文章的錯誤。 showImg(https://segmentfault.com/img/bVbkpfb?w=3106&h=2071);上篇文章《Python是否支持復制字符串呢?》剛發(fā)出一會,...
閱讀 2254·2021-11-23 09:51
閱讀 1085·2021-11-22 15:35
閱讀 4880·2021-11-22 09:34
閱讀 1622·2021-10-08 10:13
閱讀 3028·2021-07-22 17:35
閱讀 2552·2019-08-30 15:56
閱讀 3090·2019-08-29 18:44
閱讀 3105·2019-08-29 15:32