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