為什么 Python 不用聲明類型?
作者:編程指北
來(lái)源:編程指北
變量為什么需要類型?
這個(gè)問(wèn)題點(diǎn)像手機(jī)為什么叫手機(jī),電腦為什么叫電腦,這種“日用而不知”的感覺(jué)。
為什么有些語(yǔ)言需要申明類型,而有些語(yǔ)言卻不需要申明類型?
前者如 C/C++、Java,后者有 Python、JS 。
首先,在討論為什么需要類型之前,先聊聊類型。
一、 類型
我們都知道,不管是中文、英文還是編程語(yǔ)言,都具有語(yǔ)法,語(yǔ)法規(guī)定了詞怎么組成句子是符合規(guī)范的。
int a = 10;是可以的,但是a int = 10; 卻不符合 C 語(yǔ)法規(guī)范。
10 + 10 是正確的,但是10 + “hello” 卻是一個(gè)無(wú)效的表達(dá)式,但是該怎么判定呢?
答案就是「類型系統(tǒng)」,類型系統(tǒng)可以構(gòu)建起一套判斷表達(dá)式是否合法的規(guī)則。
對(duì)于每個(gè)變量都賦予一個(gè)類型,對(duì)于每種類型都定義了一組操作集合,而變量作為類型的實(shí)例,自然的繼承了這些操作。
比如我們可以規(guī)定int + int、string + string 是合法的操作,直接禁止int + string等操作。
通過(guò)顯示的將變量賦予類型,我們便可以在編譯時(shí)去檢查是否合法。
二、 變量
之前我在指針的那篇文章種說(shuō)到:變量名是變量地址的符號(hào)化,變量名是為了讓我們編程時(shí)更加方便,對(duì)人友好,可計(jì)算機(jī)可不認(rèn)識(shí)什么變量 a,它只知道地址和指令。
所以當(dāng)你去查看 C 語(yǔ)言編譯后的匯編代碼,就會(huì)發(fā)現(xiàn)變量名消失了,取而代之的是一串串抽象的地址。
可以認(rèn)為,編譯器會(huì)自動(dòng)維護(hù)一個(gè)映射,將我們程序中的變量名轉(zhuǎn)換為變量所對(duì)應(yīng)的地址,然后再對(duì)這個(gè)地址去進(jìn)行讀寫操作。
三、類型規(guī)定了什么?
我個(gè)人覺(jué)得類型首先是定義了一組操作,這是語(yǔ)法層面。
那所有的高級(jí)語(yǔ)言最后都會(huì)變成機(jī)器語(yǔ)言執(zhí)行(解釋型除外),再高級(jí)一點(diǎn),那就是匯編,我們知道匯編語(yǔ)言是由一條條指令構(gòu)成的。
而對(duì)于任何一條指令,最重要的有兩點(diǎn):數(shù)據(jù)的長(zhǎng)度、數(shù)據(jù)在哪。
比如mov dest,src將一個(gè)字節(jié)從源地址 src 傳送到目的地址 dest。
實(shí)際上,數(shù)據(jù)在哪就是地址,數(shù)據(jù)長(zhǎng)度其實(shí)某方面就是取決于類型。
也正是這樣,我之前在指針那篇文章中提到:指針的本質(zhì)就是存儲(chǔ)別的變量的地址,那么指針的類型是干什么用的呢?
是的,就是去訪問(wèn)指針?biāo)赶虻牡刂返臅r(shí)候,指針的類型和指針的值(變量地址),提供了一條匯編指令最關(guān)鍵的兩點(diǎn):數(shù)據(jù)的長(zhǎng)度和數(shù)據(jù)地址。
所以,類型除了提供語(yǔ)法檢查以外,還能夠明確變量所占據(jù)的內(nèi)存空間。
同樣也是規(guī)定了對(duì)字節(jié)的解釋方式,同樣 4 個(gè)byte,按照 int 和 float 類型解釋得到的值完全不同。
更為本質(zhì)的是,最終執(zhí)行指令的 CPU 是區(qū)分了數(shù)據(jù)的類型的,不同的類型用不同的運(yùn)算器,比如有 CPU 整形運(yùn)算和 CPU 浮點(diǎn)型運(yùn)算,不過(guò)浮點(diǎn)運(yùn)算最為擅長(zhǎng)的應(yīng)該是 GPU。
也正是因?yàn)?CPU 區(qū)分了類型,上層的匯編、高級(jí)語(yǔ)言才需要區(qū)分變量的類型,CPU 基礎(chǔ)決定了上層建筑。
四、 編譯型&解釋型
為什么 C/C++、Java 這類編譯型語(yǔ)言就需要顯示聲明類型,而 Python 這類解釋型語(yǔ)言就不需要呢?
所以是所有的編譯型都需要申明類型?所有的解釋型都不需要?
這個(gè)理解是不對(duì)的。
首先,“編譯”和“解釋”是語(yǔ)言實(shí)現(xiàn)的特征, 語(yǔ)言本身可以以任何一種方式實(shí)現(xiàn)。
可能我們大多只了解過(guò) C 編譯器(如GCC 、Clang ),卻很少聽(tīng)過(guò) C 解釋器,如 Ch。
但是,即使使用 Ch 解釋器,我們?nèi)稳恍枰?Ch 解釋的 C 代碼中申明類型。
為什么 Python 可以不用申明類型?
因?yàn)?Python 中的對(duì)象模型本身在運(yùn)行時(shí)自帶了“類型”這個(gè)信息,下面是 CPython 源碼中關(guān)于對(duì)象的定義:

這個(gè)ob_type就是用來(lái) Python 運(yùn)行時(shí)檢測(cè)變量類型的,而 Python 中的所有變量本質(zhì)上都是一個(gè)對(duì)象的“指針”。
也正因?yàn)槿绱耍琍ython 中的各種變量之間才能隨意賦值,因?yàn)橘x值的本質(zhì)其實(shí)就是改變一下變量所指向的對(duì)象。
而真正到了解釋執(zhí)行的時(shí)候,Python 解釋器就會(huì)取出這個(gè)類型字段,來(lái)判斷操作是否符合語(yǔ)法,不符合的話就會(huì)運(yùn)行時(shí)報(bào)錯(cuò)。
這個(gè)_typeobject 結(jié)構(gòu)體的內(nèi)部就定義了對(duì)象一系列的操作,比如 hashfunc、getattrfunc、setattrfunc等。

那么,為什么 C 即使是解釋執(zhí)行也需要申明類型呢?
很簡(jiǎn)單,因?yàn)?C 的對(duì)象模型中不會(huì)有”類型“這個(gè)字段,是 int 變量那直接就是 4 個(gè)字節(jié),char 就 一個(gè)字節(jié)。
而 Python 的 int 變量,除了4 個(gè)字節(jié)的值本身以外,還需要占據(jù)一系列的描述類型的空間。
也就是說(shuō) C 語(yǔ)言運(yùn)行時(shí)完全沒(méi)有類型這種信息,所以需要在編譯、解釋的時(shí)候就帶上。
之前偶爾會(huì)聽(tīng)到有人說(shuō) Python 是無(wú)類型的,這是完全錯(cuò)誤的,看過(guò)這篇文章后,相信你一定不會(huì)說(shuō)錯(cuò)了。
當(dāng)然了,本文也只是簡(jiǎn)單的從粗淺的語(yǔ)法和實(shí)現(xiàn)層面解釋了下,關(guān)于類型系統(tǒng)、類型推導(dǎo)等有人在研究的,這個(gè)也不是咱擅長(zhǎng)的地方,感興趣可以去搜一下。
Python貓注:關(guān)于 Python 的類型話題,推薦閱讀:《Python到底是強(qiáng)類型語(yǔ)言,還是弱類型語(yǔ)言?》、《辨析編程語(yǔ)言的四種類型:動(dòng)靜類型與強(qiáng)弱類型》

還不過(guò)癮?試試它們
▲Python最佳代碼實(shí)踐:性能、內(nèi)存和可用性!
▲Python在計(jì)算內(nèi)存時(shí)應(yīng)該注意的問(wèn)題?
▲Python進(jìn)階:如何將字符串常量轉(zhuǎn)為變量?
