1. 真的能用隱式類型轉換做爲強弱類型的判斷標準嗎?
最近有些學員問我,Python究竟是強類型語言,仍是弱類型語言。我就直接脫口而出:Python是弱類型語言。沒想到有一些學員給我了一些文章,有中文的,有英文的,都說Python是強類型語言。我就很好奇,特地仔細研究了這些文章,例如,下面就是一篇老外寫的文章:
https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic%20language%20and%20also%20a%20strongly%20typed%20language
其餘中文的相關文章,你們能夠去網上搜,一堆,這裏就不一一列舉了。
我先不說這些結論對不對,我先總結一下這些文章的核心觀點。這些文章將編程語言分爲強類型、弱類型、動態類型和靜態類型。這4個概念的解釋以下:
強類型:若是一門語言不對變量的類型作隱式轉換,這種編程語言就被稱爲強類型語言 ;
弱類型:與強類型相反,若是一門語言對變量的類型作隱式轉換,那咱們則稱之爲弱類型語言;
動態類型:若是一門語言能夠在運行時改變變量的類型,那咱們稱之爲動態類型語言;
靜態類型:與動態類型相反,若是一門語言不能夠在運行時改變變量的類型,則稱之爲靜態類型語言;

其實這些概念就涉及到編程語言的兩個特性:隱式類型轉換和類型固化。
所謂類型固化,就是指一旦變量在初始化時被肯定了某個數據類型(如整數類型),那麼這個變量的數據類型將永遠不會變化。
關於動態類型和靜態類型,在本文的後面再來討論,這裏先探討強類型和弱類型。
如今姑且認爲這個結論沒問題。強類型就是不容許作隱式類型轉換。OK,咱們看看用這個隱式類型轉換來判斷強類型和弱類型是否合理。
在這些文章中,給出了不少例子做爲證據來證明這個結論,其中最典型的例子是在Python語言中,int + string是不合法的,沒錯,確實不合法。如執行1 + 'abc'會拋出異常。固然,還有人給出了另外一個例子:string / int也是不合法的,如執行'666' / 20會拋出異常,沒錯,字符串與整數的確不能直接相除。那你怎麼不用乘號舉例呢?如'abc' * 10,這在Python中但是合法的哦,由於這個表達式會將'abc'複製10份。爲什麼不用我大乘號來舉例,難道瞧不起我大乘號嗎?這是運算符歧視?

另外,難道沒據說過Python支持運算符重載嗎?經過運算符重載,可讓兩個類型徹底不一樣的變量或值在一塊兒運算,如相加,看下面的例子:
class MyClass1: def __init__(self,value): self.value = value
class MyClass2: def __init__(self,value): self.value = valuemy1 = MyClass1(20)my2 = MyClass2(30)print( my1 + my2)
若是執行這段代碼,100%會拋出異常,由於MyClass1和MyClass2確定不能相加,但若是按下面的方式修改代碼,就沒問題了。
class MyClass1: def __init__(self,value): self.value = value def __add__(self,my): return self.value + my.valueclass MyClass2: def __init__(self,value): self.value = value def __add__(self,my): return self.value + my.value
my1 = MyClass1(20)my2 = MyClass2(30)
print( my1 + my2)
這段代碼對MyClass1和MyClass2進行了加法運算符重載,這樣兩個不一樣類型的變量就能夠直接相加了,從表面上看,好像是發生了類型轉換,但實際上是運算符重載。
固然,運算符重載也可能會使用顯式類型轉換,以下面的代碼容許不一樣類型的值相加。
class MyClass1: def __init__(self,value): self.value = value def __add__(self,my): return str(self.value) + str(my.value)class MyClass2: def __init__(self,value): self.value = value def __add__(self,my): return str(self.value) + str(my.value)
my1 = MyClass1(20)my2 = MyClass2("xyz")
print( my1 + my2)
其實這段代碼也就至關於int + string形式了,只是用MyClass1和MyClass2包裝了一層。可能有不少同窗會說,這能同樣嗎?明顯不是int + string的形式,ok,的確是不太同樣。
惋惜目前Python還不支持內建類型(如int、str)的運算符重載,但不能保證之後不支持,若是之後Python要是支持內建類型運算符重載,那就意味着能夠重載str類的__add__方法了,目前str類定義在builtins.py文件中,裏面已經預約義了不少可能被重載的運算符。固然,目前Python是直接將這些運算符方法固化在解析器中了,例如,__add__方法是隻讀的,不能修改。以下面的Python代碼至關於a + "ok"。
a = "abc"print( a.__add__("ok"))
但你不能用下面的代碼覆蓋掉str類的__add__方法。
def new_add(self, value): return str(self) + str(value)str.__add__ = new_add
執行這段代碼會拋出以下圖的異常,也就是說,目前Python的內建類型,如str,是不能動態爲其添加新的成員或覆蓋之前的成員的。

但如今不能,不表明之後不能。若是之後Python支持覆蓋內建類型的運算符,那麼int + string就可讓其合法化。不過可能還會有同窗問,就算內建類型支持運算符重載,那不還須要使用顯式類型轉換嗎?是的,沒錯,須要類型轉換。
如今咱們先來談談類型轉換,先用另一個被公認的弱類型編程語言JavaScript爲例。在JS中,1 + 'abc'是合法的、'444'/20也是合法的,因此就有不少人認爲js是弱類型語言,沒錯,js的確是弱類型語言。但弱類型確實是根據1 + 'abc'和'444'/20得出來的?
有不少人認爲,JavaScript不作類型檢查,就直接將1和'abc'相加了!你是當真的?若是不作類型檢查,那麼js怎麼會知道如何將1和'abc'相加,爲啥不將1當作1.0呢?其實無論是什麼類型的編程語言,數據類型檢測都是必須的,無論是js、仍是Python,或是Java,內部必定會作數據類型檢測,只是檢測的目的不一樣而已。在Python中,進行數據類型檢測後,發現不合規的狀況,有時會自動處理(如int+float),有時乾脆就拋出異常(如int + string)。而在Java中就更嚴格了,在編譯時,發現不合規的狀況,就直接拋出編譯錯誤了。在js中,發現不合規的狀況,就會按最大可能進行處理,在內部進行類型轉換。對,不是無論數據類型了,而是在內部作的數據類型轉換。那麼這和經過Python的運算符重載在外部作類型轉換有什麼區別呢?只是一個由編譯器(解析器)內部處理的,一個是在外部由程序員編寫代碼處理的!並且就算Python不會支持內建類型的運算符重載,那麼也有可能直接支持int + string的形式。由於目前Python不支持,因此正確的Python代碼不可能有int + string的形式。因此若是之後支持int + string的形式,也能夠徹底作到代碼向下兼容。就算Python將來不支持int + string形式,那麼我本身作一個Python解析器(例如,咱們團隊如今本身作的Ori語言,支持類型隱式轉換,不過其實是生成了其餘的編程語言,也就是語言之間的轉換,這是否是表明Ori是弱類型語言呢?),徹底兼容Python的代碼,只不過支持int+string形式,那麼能不能說,個人這個Python版本是弱類型Python呢?這很正常,由於像C++這種語言也有不少種實現版本,Python一樣也能夠擁有,只不過目前沒多少人作而已,但不等於沒有可能。
若是Python真這麼作了,那麼能不能說Python又從強類型語言變成了弱類型語言呢?若是你們認爲一種語言的類型強弱是能夠隨着時間變化的,那麼我無話可說!
總之,須要用一種肯定不會變的特性來表示強弱類型纔是最合適的。一般來說,某種語言的變量一旦數據類型肯定了,就不容許變化了,這種才能夠稱爲強類型,強大到類型一言九鼎,類型一旦肯定,就不容許變了,而Python顯然不是,x = 20; x = 'abc';一樣是合法的,x前後分別是int和str類型。
PS:這裏再給你們一個表,一般編程語言中肯定類型是否兼容,就是經過相似的表處理的。這個表主要用於內建類型,若是是自定義類型,須要經過接口(實現)和類(繼承)類肯定類型是否兼容。
這個表只給出了3個數據類型:int、float和str。根據業務不一樣,這個表能夠有多種用途,例如,賦值,是否能夠進行運算等。這裏就只考慮進行加法運算。其中True表示容許進行加法運算,False表示不容許進行加法運算,很顯然,若是是int + int形式,第1個操做數能夠從第1列查找,第2個操做數能夠從第1行查找,找到了(1,1)的位置,該位置是True,因此int + int是合法的,int + float,float + float、str + str的情形相似,若是遇到int + str,就會找到(1,3)或(3,1),這兩個位置都是False,就代表int + str是不合法的。其實Python和JavaScript都進行到了這一步。只不過Python就直接拋出了異常,而JS則嘗試進行類型轉換,但都須要進行類型檢測。由於類型轉換須要肯定數據類型的優先級,優先級低的會轉換爲優先級高的類型,如str的優先級比int高,因此int會轉換爲str類型。float比int高,因此int會轉換爲float類型,這就涉及到另一個類型優先級表了。
根據這個表可知,編程語言只是在遇到類型不合規的狀況下處理的方式不一樣,這就是編譯器(解析器)的業務邏輯了,這個業務邏輯隨時可能變(一般不會影響程序的向下兼容),因此是不能用這一特性做爲強弱語言標識的,不然強類型和弱類型語言就有可能會不斷切換了,由於編程語言會不斷進化的。
那麼爲何能夠用類型固化做爲強弱類型的標識呢?由於類型固化一般是不可變的,那麼爲何是不可變的呢?下面用Python來舉例:
下面的Python代碼是合法的。x從int變成了str,類型並無固化,全部Python是弱類型語言。
那麼有沒有可能Python之後對類型進行固化呢?從技術上來講,徹底沒問題,但從代碼兼容性問題上,將會形成嚴重的後果。由於類型沒固化屬於寬鬆型,一旦類型固化,屬於嚴格型。之前已經遺留了不少寬鬆的代碼,一旦嚴格,那麼就意味着x = 'abc'將會拋出異常,就會形成不少程序沒法正常運行。因此若是Python這麼作,就至關於一種新語言了,如PythonX,而不能再稱爲Python了。就像人類進化,不管從遠古的尼安德特人,仍是智人,或是現代各個國家的人,不管怎麼進化,都須要在主線上發展,例如,都有一個腦殼,兩條腿,兩個胳膊。固然,可能細節不一樣,如黑眼睛,黃頭髮等。你不能進化出兩個頭,8條腿來,固然能夠這麼進化,但這個就不能再稱爲人了,就是另一種生物了。

如今再看一個相反的例子,若是一種編程語言(如Java)是強類型的,可否之後變成弱類型語言呢?
其實從技術上和兼容性上這麼作是沒問題的。但也會有不少其餘問題,如編譯器(或運行時)的處理方式徹底不一樣,咱們知道,類型固化的程序要比類型不固化的程序運行效率高,由於類型不固化,須要不斷去考慮類型轉換的問題。並且在空間分配上更麻煩,有可能會不斷分配新的內存空間。例如,對於一個數組來講,js和python(就是列表)是能夠動態擴容的,其實這個方式效率很低,須要用算法在合理的範圍內不斷分配新的內存空間,而Java不一樣,數組一旦分配內存空間,是不可變的,也就是空間固化(相似於類型固化),這樣的運行效率很是高。
因此一旦編程語言從類型固化變成類型不固化,儘管能夠保證代碼的兼容性,但編譯器或運行時的內部實現機理將徹底改變,因此從本質上說,也是另一種編程語言了。就像人類的進化,儘管從表面上符合人類的全部特徵。但內部已經變成生化人了,已經不是血肉之軀了,這也不能稱爲人類了。
因此不管往哪一個方向變化,都會造成另一種全新的編程語言,因此用類型固化來做爲強弱類型標識是徹底沒有問題的。
3. C++、Java、Kotlin是強類型語言,仍是弱類型語言
我看到網上有不少文章將C++歸爲弱類型語言。其實,這我是頭一次據說C++有人認爲是弱類型語言,是由於C++支持string+int的寫法嗎?沒錯,C++是支持這種寫法,但直接這麼寫,語法沒問題,但不會獲得咱們指望的結果,以下面的代碼:
std::cout << "Hello, World!" + 3 << std::endl;
這行代碼並不會輸出Hello,World!3,要想輸出正常的結果,須要進行顯式類型轉換,代碼以下:
std::cout << "Hello, World!" + std::to_string(3) << std::endl;
儘管C++編譯器支持string+int的寫法,但得不到咱們指望的結果,因此C++的string和int相加須要進行轉換。所以,僅僅經過string+int或相似的不一樣類型不能直接在一塊兒運算來判斷語言是不是強類型和弱類型的規則是站不住腳的。並且C++也支持運算符重載,也就意味着可讓"abc" + 4變成不合法的。
那麼Java是強類型仍是弱類型呢?Java是強類型語言,由於不少文章給出了下面的例子(或相似):
是的,這個表達式會出錯,但你不要忘了,Java支持下面的表達式:
這行表達式輸出了6664,爲啥不用加號(+)舉例呢?前面歧視Python的乘號,如今又歧視Java裏的加號嗎?其實這是由於前面描述的類型優先級問題,因爲string的優先級高於int,所以4會轉換爲"4"。因此"666" / 4其實會也會發生隱式類型轉換,變成"666"/"4",兩個字符串天然不能相除了,而"666" + 4會變成"666" + "4",兩個字符串固然能夠相加了。這就是個語義的問題,和強弱類型有毛關係。

因此嗎?Java是強類型語言沒錯,但判斷依據錯了。
Kotlin是強類型和弱類型呢?答案是Kotlin是強類型語言。不過Kotlin支持運算符重載,看下面的代碼。
class MyClass(var value: Int) { operator fun plus(other: Int): Int { return value + other; }}fun main() { var my: MyClass = MyClass(200); print(my + 20); }
咱們都知道,Kotlin也是JVM上的一種編程語言(儘管能夠生成js,但須要用Kotlin專有API),而Java是不支持運算符重載的,在同一個運行時(JVM)上,有的語言支持運算符重載,有的語言不支持運算符重載。從這一點就能夠看出,運算符來處理兩側的操做數,只不過是個語法糖而已。想讓他支持什麼樣的運算均可以,如,"abcd" / "cd",其實也可讓他合法化,例如,語義就表示去掉分子以分母爲後綴的子字符串,若是沒有該後綴,分子保持不變,因此,"abcd"/"cd"的結果就是"ab",而"abcd"/"xy"的結果仍是"abcd",語法糖而已,與強弱類型沒有半毛錢關係。
如今來講說靜態語言和動態語言。有人說能夠用是否實時(在運行時)改變變量類型判別是靜態語言仍是動態語言,沒錯,變量類型的實時改變確實是動態語言的特徵之一,但並非所有。動態語言的另外一些特徵是能夠隨時隨地爲類【或其餘相似的語法元素】(主要是指自定義的類,有一些語言可能不支持對內建類型和系統類進行擴展)添加成員(包括方法、屬性等)。
例如,下面的JavaScript代碼動態爲MyClass類添加了一個靜態方法(method1)和一個成員方法(method2)。
class MyClass {
}MyClass.method1 = function () { console.log('static method');}
MyClass.method1() var my = new MyClass();my.method2 = function () { console.log('common method')}my.method2()
Python動態添加成員的方式與JavaScript相似,代碼以下:
class MyClass: pass
def method1(): print('static method') MyClass.method1 = method1
MyClass.method1() my = MyClass()
def method2(): print('common method')my.method2 = method2my.method2()
還有就是數組的動態擴容(根據必定的算法,並非每一次調用push方法都會增長內存空間),如JavaScript的代碼:
a = []a.push("hello")a.push(20)a.push("world")console.log(a)
a = []a.append('world')a.append(20)a.append("hello")print(a)
這些特性在靜態語言(如Java、C++)中是沒法作到的。在靜態語言中,一個類一旦定義完,就不能再爲類動態添加任何成員和移除任何成員,除非修改類的源代碼。
因此說,靜態和動態其實涵蓋了多個方面,如類型固化,動態擴展、數組擴容等。而強類型和弱類型的特性其實只能算靜態和動態的特性之一。也就是說,說一種語言是靜態語言,其實已經包含了這種語言的變量類型一旦肯定不可改變的事實,也就是靜態語言必定是強類型的編程語言。
這句話看起來沒毛病,也能看懂,但實際上是有語病的。由於前面已經說了這我的是一個男人了,後面就不必強調是男演員了,而只須要按下面說的便可:
應該用固定不變的特性來標識一種語言的特性。而語言是否支持隱式類型轉換,這只是編譯器或運行時的內部業務邏輯,至關於語法糖而已,是隨時能夠改變的。而類型固化,動態擴展、數組擴容,這些涉及到編程語言的根本,一旦改變,就變成了另一種語言了,因此一般用這些特性標識語言的特性。一般來說,靜態語言的效率會高於動態語言。由於,這些動態特性會讓程序有更大負擔,如類型不固定,就意味着可能會爲新的類型分配新的內存空間,動態擴展和數組擴容也意味着不斷進行邊界檢測和分配新的內存空間(或回收舊的內存空間)。這就是爲何C++、Java、C#等編程語言的性能要高於js、Python的主要緣由。
其實過分強調靜態、動態、強類型、弱類型,意義並不大。覺得編程語言之後的發展方向是靜態語言動態化,弱類型強類型化。都是互相滲透了,若是之後出現一種編程語言,同時擁有靜態和動態的特性,其實並不稀奇。例如,儘管變量類型不容許改變,但容許動態爲對象添加成員。就和光同樣,既是光子(粒子),又是電磁波,也就是說光擁有波粒二象性! 編程語言也同樣,也會同時擁有靜動態二象性!

對本文感興趣,能夠加李寧老師微信公衆號(unitymarvel):

關注 「極客起源」 公衆號,得到更多免費技術視頻和文章。
