1. 程式人生 > >Python 到底是強型別語言,還是弱型別語言?

Python 到底是強型別語言,還是弱型別語言?

## 0、前言 我在上一篇文章中分析了 [為什麼 Python 沒有 void 型別](https://mp.weixin.qq.com/s/wWCgwMofSvKBbi3gZLxsAQ) 的話題,在文章釋出後,有讀者跟我討論起了另一個關於型別的問題,但是,我們很快就出現了重大分歧。 我們主要的分歧就在於:**Python 到底是不是強型別語言?** 我認為是,而他認為不是。 他寫了一篇很長的文章《[誰告訴的你們Python是強型別語言!站出來,保證不打你!](https://blog.csdn.net/nokiaguy/article/details/108218260)》,專門重申了他的觀點,但可惜錯漏百出。 我曾有想法要寫寫關於 Python 型別的問題,現在藉著這個機會,就來系統地梳理一下吧。 (PS:在我寫作進行到差不多一半的時候,微信讀者群裡恰好也討論到“強弱型別”的話題!在與大家討論時,我的一些想法得到了驗證,同時我也學到了很多新知識,所以本文的部分內容有群友們的功勞,特此鳴謝!) ## 1、動靜型別與強弱型別 很多讀者應該都熟悉`動態型別` 與`靜態型別` ,但是很多人也會把它們跟`強弱型別` 混為一談,所以我們有必要先作一下概念上的澄清。 這兩組型別都是針對於程式語言而言的,但關注的核心問題不同。 **對於“動靜型別”概念,它的核心問題是“什麼時候知道一個變數是哪種型別”?** **一般而言,在編譯期就確定變數型別的是靜態型別語言,在執行期才確定變數型別的則是動態型別語言。** 例如,某些語言中定義函式“int func(int a){...}”,在編譯時就能確定知道它的引數和返回值是 int 型別,所以是靜態型別;而典型如 Python,定義函式時寫“def func(a):...”,並不知道引數和返回值的型別,只有到執行時呼叫函式,才最終確定引數和返回值的型別,所以是動態型別 **對於“強弱型別”概念,它的核心問題是“不同型別的變數是否允許隱式轉化”?** **一般而言,編譯器有很少(合理)隱式型別轉化的是強型別語言,有較多(過分)隱式型別轉化的是弱型別語言。** 例如,Javascript 中的 `"1000"+1` 會得到字串“10001”,而 `"1000"-1` 則會得到數字 999,也就是說,編譯器根據使用場合,對兩種不同型別的物件分別做了隱式的型別轉化,但是相似的寫法,在強型別語言中則會報型別出錯。(數字與字串的轉化屬於過分的轉化,下文會再提到一些合理的轉化。) 按照以上的定義,有人將常見的程式語言畫了一張分類圖: ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gi5r2xkihgj20dw09dwec.jpg) 按強弱型別維度的劃分,可以歸納出: - 強型別:Java、C#、Python、Ruby、Erlang(再加GO、Rust)…… - 弱型別:C、C++、Javascript、Perl、PHP、VB…… ## 2、過去的強弱型別概念 動靜型別的概念基本上被大家所認可,然而,強弱型別的概念在問答社群、技術論壇和學術討論上卻有很多的爭議。此處就不作羅列了。 為什麼會有那麼多爭議呢? **最主要的原因之一是有人把它與動靜型別混用了。** 最明顯的一個例子就是 Guido van Rossum 在 2003 年參加的一個訪談,它的話題恰好是關於強弱型別的([Strong versus Weak Typing](https://www.artima.com/intv/strongweak.html)): ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gi5r1xokqgj20cs09ggnr.jpg) 但是,他們談論的明顯只是動靜型別的區別。 訪談中還引述了 Java 之父 James Gosling 的話,從他的表述中也能看出,他說的“強弱型別”其實也是動靜型別的區分。 另外還有一個經典的例子,C 語言之父 Dennis Ritchie 曾經說 C 語言是一種**“強型別但是弱檢查”**的語言。如果對照成前文的定義,那他其實指的是“靜態型別弱型別”。 為什麼這些大佬們會有混淆呢? 其實原因也很簡單,**那就是在當時還沒有明確的動靜型別與強弱型別的概念之分!或者說,那時候的強弱型別指的就是動靜型別。** 維基百科上給出了 1970 年代對強型別的定義,基本可以還原成前文提到的靜態型別: > In 1974, Liskov and Zilles defined a strongly-typed language as one in which "whenever an object is passed from a calling function to a called function, its type must be compatible with the type declared in the called function."[[3\]](https://en.wikipedia.org/wiki/Strong_and_weak_typing#cite_note-2) In 1977, Jackson wrote, "In a strongly typed language each data area will have a distinct type and each process will state its communication requirements in terms of these types."[[4\]](https://en.wikipedia.org/wiki/Strong_and_weak_typing#cite_note-3) 前面幾位程式語言之父應該就是持有類似的觀念。 不過,大佬們也意識到了當時的“強弱型別”概念並不充分準確,所以 Dennis Ritchie 才會說成**“強型別但是弱檢查”,**而且在訪談中,Guido 也特別強調了 Python 不應該被稱為弱型別,而應該說是**執行時型別(runtime typing)** 。 但是在那個早期年代,基本上強弱型別就等同於動靜型別,而這樣的想法至今仍在影響著很多人。 ## 3、現在的強弱型別概念 早期對於程式語言的分類其實是混雜了動靜與強弱兩個維度,但是,它們並不是一一對應重合的關係,並不足以表達程式語言間的區別,因此就需要有更為明確/豐富的定義。 有人提出了“type safety”、“memory safety”等區分維度,也出現了靜態檢查型別和動態檢查型別,與強弱型別存在一定的交集。 直到出現 2004 年的一篇集大成的學術論文《[Type Systems](http://lucacardelli.name/Papers/TypeSystems.pdf)》(出自微軟研究院,作者 Luca Cardelli),專門研究程式語言的不同型別系統: ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gi5r2jaof0j20cs09gmze.jpg) 論文中對於強弱檢查(也即強弱型別)有一個簡短的歸納如下: - Strongly checked language: A language where no forbidden errors can occur at run time (depending on the definition of forbidden error). - Weakly checked language: A language that is statically checked but provides no clear guarantee of absence of execution errors. 其關鍵則是程式對於 *untrapped errors* 的檢查強度,在某些實際已出錯的地方,弱型別程式並不作捕獲處理,例如 C 語言的一些指標計算和轉換,而《[C 程式設計師十誡](http://doc.cat-v.org/henry_spencer/ten-commandments)》的前幾個都是弱型別導致的問題。 ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gi7jc5jq0kj20cs09gjt0.jpg) 論文對於這些概念的定義還是比較抽象的,由於未捕獲的錯誤(untrapped errors)大多是由於隱式型別轉換所致,所以又演化出了第一節中的定義,以隱式型別轉換作為判斷標準。 如今將**“對隱式型別轉換的容忍度”**作為強弱型別的分類標準,已經是很多人的共識(雖然不夠全面,而且有一些不同的聲音)。 例如,維基百科就把隱式型別轉換作為弱型別的主要特點之一: > A weakly typed language has looser typing rules and may produce unpredictable results or may perform implicit type conversion at runtime. 例如,以 Python 為例,**社群的主流看法認為它是強型別語言,而判斷的標準也是看隱式型別轉換。** 例子有很多,比如 Python 官方的 wiki,它專門回答了[Why is Python a dynamic language and also a strongly typed language](https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic%20language%20and%20also%20a%20strongly%20typed%20language) ,給出了 4 個答案,為 Python 的“動態強型別”定性: ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gi7jdo43yuj20cs09gdib.jpg) 再比如,在《流暢的Python》第11章的雜談中,也專門提到了強弱型別的分類。(它的用語是“很少隱式型別轉換”,算是比較嚴謹的,但是也錯誤地把 C++ 歸為了強型別。) ## 4、Python 是不是強型別語言? 關於“Python 是否屬於強型別”話題,在主流觀點之外,還存在著不少誤解的看法。 一方面的原因有些人混用了強弱型別與動靜型別,這有歷史的原因,前面已經分析了。 另外還有一個同樣重要的原因,即有人把弱型別等同於“完全沒有隱式型別轉換”了,這種想法並不對。 **事實上,強弱型別的概念中包含著部分相對主義的含義,強型別語言中也可能有隱式型別轉換。** 比如,Rust 語言為了實現“記憶體安全”的設計哲學,設計了很強大的型別系統,但是它裡面也有隱式型別轉換(自動解引用)。 問題在於:**怎麼樣的隱式型別轉換是在合理範圍內的?以及,某些表面的隱式型別轉換,是否真的是隱式型別轉換?** 回到 Python 的例子,我們可以分析幾種典型的用法。 比如,`"test"*3` 這種字串“乘法”運算,雖然是兩種型別的操作,但是並不涉及隱式型別轉換轉化。 比如,`x=10; x="test"` 先後給一個變數不同型別的賦值,表面上看 x 的型別變化了,用 type(x) 可以判斷出不同,但是,Python 中的型別是跟值繫結的(右值繫結),並不是跟變數繫結的。 變數 x 準確地說只是變數名,是繫結到實際變數上的一個標籤,它沒有型別。type(x) 判斷出的並不是 x 本身的型別,而是 x 指向的物件的型別,就像內建函式 id(x) 算出的也不是 x 本身的地址,而是實際的物件的地址。 比如,`1 + True` 這種數字與布林型別的加法運算,也沒有發生隱式型別轉換。因為 Python 中的布林型別其實是整型的子類,是同一種類型!(如果有疑問,可查閱 [PEP-285](https://github.com/chinesehuazhou/peps-cn/blob/master/StandardsTrack/285--%E6%B7%BB%E5%8A%A0%E5%B8%83%E5%B0%94%E7%B1%BB%E5%9E%8B.md)) 再比如,整數/布林值與浮點數相加,在 Python 中也不需要作顯式型別轉換。但是,它的實現過程其實是用了數字的`__add__()` 方法,Python 中一切皆物件,數字物件也有自己的方法。(其它語言可不一定) 也就是說,**數字間的算術運算操作,其實是一個函式呼叫的過程,跟其它語言中的算術運算有著本質的區別。** **另外,不同的數字型別雖然在計算機儲存層面有很大差異,但在人類眼中,它們是同一種類型(寬泛地分),所以就算髮生了隱式型別轉換,在邏輯上也是可以接受的。** 最後,還有一個例子,即 Python 在 if/while 之後的真值判斷,我之前分析過它的[實現原理](https://mp.weixin.qq.com/s/g6jZX0IdH9xpM7BMV3-ToQ) ,它會把其它型別的物件轉化成布林型別的值。 但是,它實際上也只是函式呼叫的結果(\_\_bool\_\_() 和 \_\_len\_\_()),是通過計算而得出的合理結果,並不屬於隱式的強制型別轉換,不在 untrapped errors 的範疇裡。 **所以,嚴格來說,前面 5 個例子中都沒有發生型別轉換。** 浮點數和真值判斷的例子,直觀上看是發生了型別轉換,但它們其實是 Python 的特性,是可控的、符合預期的、並沒有對原有型別造成破壞。 退一步講,若放寬“隱式型別轉換”的含義,認為後兩個例子發生了隱式型別轉換,但是,它們是通過嚴謹的函式呼叫過程實現的,也不會出現 forbidden errors,所以還是屬於強檢查型別。 ## 5、其它相關的問題 前文對概念的含義以及 Python 中的表現,作了細緻的分析。接下來,為了邏輯與話題的完整性,我們還需要回答幾個小問題: (1)能否以“隱式型別轉換”作為強弱型別的分類依據? 明確的分類定義應該以《[Type Systems](http://lucacardelli.name/Papers/TypeSystems.pdf)》為準,它有一套針對不同 error 的分類,強弱型別其實是對於 forbidden errors 的處理分類。隱式型別轉換是其明顯的特徵,但並不是全部,也不是唯一的判斷依據。 本文為了方便理解,使用這個主要特徵來劃分強弱型別,但是要強調,強型別不是沒有隱式型別轉換,而是可能有很少且合理的隱式型別轉換。 (2)假如有其它直譯器令 Python 支援廣泛的隱式型別轉換,那 Python 還是強型別語言麼? 語言的標準規範就像是法律,而直譯器是執法者。如果有錯誤的執法解釋,那法律還是那個法律,應該改掉錯誤的執法行為;如果是法律本身有問題(造成了解釋歧義和矛盾,或者該廢棄),那就應該修改法律,保證它的確定性(要麼是強型別,要麼是弱型別)。 (3)為什麼說 Javascript 是弱型別? 因為它的隱式型別轉換非常多、非常複雜、非常過分!比如,Javascript 中`123 + null` 結果為 123,`123 + {} ` 結果為字串“123[object Object]”。 另外,它的雙等號“\=\=”除了有基本的比較操作,還可能發生多重的隱式型別轉換,例如`true==['2']` 判斷出的結果為 false,而`true==['1']` 的結果是 true,還有`[]==![]` 和`[undefined]==false` 的結果都為 true…… (4)C++ 是不是弱型別語言? 前文提到《流暢的Python》中將 C++ 歸為強型別,但實際上它應該被歸為弱型別。C++ 的型別轉換是個非常複雜的話題,@櫻雨樓 小姐姐曾寫過一個系列文章做了系統論述,文章地址:[如何攻克 C++ 中複雜的型別轉換?](https://mp.weixin.qq.com/s/lJiva3BUJXUV0cpX1dOe2Q) 、[詳解 C++ 的隱式型別轉換與函式過載!](https://mp.weixin.qq.com/s/S_1KPn_GWJ7hmLH19Dajfg) 、[誰說 C++ 的強制型別轉換很難懂?](https://mp.weixin.qq.com/s/q3iwtvqMSp6lNC_ZR_SP6A) ## 6、小結 強弱型別概念在網上有比較多的爭議,不僅在 Python 是如此,在 C/C++ 之類的語言更甚。 **其實在學術上,這個概念早已有明確的定義,而且事實上也被很多人所接納。** 那些反對的聲音大多是因為概念混用,因為他們忽略了另一種對語言進行分類的維度;同時,還有一部分值得注意的原因,即不能認為強型別等於“完全無隱式型別轉換”或“只要沒有xxx隱式型別轉換”。 本文介紹了社群中對 Python 的主流分類,同時對幾類疑似隱式型別轉換的用法進行了分析,論證出它是一種強型別語言。 文章體現了作者一貫的刨根問底精神,這是“[Python為什麼](https://github.com/chinesehuazhou/python-whydo)”系列文章的風格,如果你喜歡本文,歡迎訂閱關注! ## 相關連結 [1] 誰告訴的你們Python是強型別語言!站出來,保證不打你!: https://blog.csdn.net/nokiaguy/article/details/108218260 [2] Strong versus Weak Typing: https://www.artima.com/intv/strongweak.html [3] https://en.wikipedia.org/wiki/Strong_and_weak_typing#cite_note-2 [4] https://en.wikipedia.org/wiki/Strong_and_weak_typing#cite_note-3 [5] Type Systems: http://lucacardelli.name/Papers/TypeSystems.pdf [6] C 程式設計師十誡: http://doc.cat-v.org/henry_spencer/ten-commandments [7] Why is Python a dynamic language and also a strongly typed language: https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic%20language%20and%20also%20a%20strongly%20typed%20language [8] PEP-285: https://github.com/chinesehuazhou/peps-cn/blob/master/StandardsTrack/285--%E6%B7%BB%E5%8A%A0%E5%B8%83%E5%B0%94%E7%B1%BB%E5%9E%8B.md [9] Type Systems: http://lucacardelli.name/Papers/TypeSystems.pdf [10] Python為什麼: https://github.com/chinesehuazhou/pyth