深入理解python之對象系統(tǒng)

我們在學(xué)習(xí)理解一個事物時嚣鄙,往往遵循著由表及里的規(guī)律。第一步鳍怨,我們學(xué)習(xí)一個事物的特性表現(xiàn)(feature)呻右。在對事物的表現(xiàn)有了掌握以后,進(jìn)一步的鞋喇,我們嘗試探索事物表現(xiàn)背后的內(nèi)在原理声滥。本文在敘述過程中,也遵循這樣一個過程侦香。第一部分是對python對象系統(tǒng)feature的簡要概述落塑,第二部分則是對python 對象系統(tǒng)底層實(shí)現(xiàn)的梳理與總結(jié),在文章的最后一部分罐韩,是對常用的內(nèi)置python類型實(shí)現(xiàn)的一個介紹憾赁。

Python 中的對象與類型的行為表現(xiàn)

我們在學(xué)習(xí)python的過程中,比較經(jīng)常聽到的一句話叫做 散吵,python處處皆對象龙考。這是對于python對象模型的高度概括的描述。Python中的變量矾睦,實(shí)際上是一個名字晦款,指向所引用的對象。Python中有“名字空間”的概念枚冗,這類似于其他編程語言中的作用域缓溅。在python中,名字空間底層由一個dict實(shí)現(xiàn)赁温,變量名就是字典中的鍵肛宋,而變量引用的對象就是字典中鍵對應(yīng)的值。

名字空間
名字空間

那么現(xiàn)在我們知道束世,不同于其他的語言酝陈,python中的整數(shù),布爾值毁涉,字符串等類型的變量均是一個對象而不是簡單的值沉帮。更進(jìn)一步的,python中的所有語法元素,全部都是對象穆壕。函數(shù)是函數(shù)對象待牵,模塊是模塊對象,最特殊的是喇勋,對象的類型也是一個對象缨该。

Python中的面向?qū)ο髾C(jī)制與java等語言,所有的類型默認(rèn)繼承自基類object川背,或者某個object的派生類型贰拿。但是由于在python中類型也是一個對象,所以包含基類object在內(nèi)的所有類型熄云,都是對象實(shí)例膨更。區(qū)別于普通對象,我們姑且把描述類型的對象叫做 類對象缴允。所有類對象的類型被叫做元類(metaclass)荚守,元類默認(rèn)是一個type類對象,或者某個繼承自type的類對象练般。最后矗漾,type類對象的類型是type自身。

類對象總結(jié):

類對象 基類 類對象的類型
普通類型 object/object 派生類 Type/type 派生類
元類 type type
Object(默認(rèn)基類) object type
Type(默認(rèn)元類) object type

Python 底層對于對象與類型的實(shí)現(xiàn)

在對python語言層面上的對象與類型(對象)的行為表現(xiàn)有了了解以后薄料,下面我們來研究一下缩功,在python的底層是如何用c語言實(shí)現(xiàn)python對象與python類型的。

對象

python對象在python實(shí)現(xiàn)中都办,用PyObject實(shí)例來描述嫡锌。Python中的對象有很多種,int類型的對象對應(yīng)于PyIntObject實(shí)例琳钉,string類型的對象對應(yīng)于PyStringObject實(shí)例势木,但是這些PyxxxObject實(shí)例同時又都是PyObject實(shí)例,假設(shè)我們用c++去實(shí)現(xiàn)python的話歌懒,那么顯而易見的是啦桌,PyObject是一個基類,各種不同類型的對象實(shí)例描述都繼承自PyObject及皂。然而甫男,python是由c去實(shí)現(xiàn)的,沒有類與繼承的概念验烧,python的開發(fā)者通過精心設(shè)計(jì)結(jié)構(gòu)體的成員和布局模擬了繼承與多態(tài)板驳。首先泛化的PyObject結(jié)構(gòu)本身非常簡單,共有兩個成員ob_refcnt,和ob_type兩個成員碍拆,其結(jié)構(gòu)如下圖:

PyObject
PyObject

其中ob_refcnt 是對象的引用計(jì)數(shù)若治,而ob_type指向了對象的類型慨蓝。

我們再來看一個特化object結(jié)構(gòu),PyIntObject端幼。它由三個成員組成 ob_refcnt,ob_type,ob_val,其結(jié)構(gòu)如下圖:

PyIntObject
PyIntObject

可以看到礼烈,PyIntObject的頭,就是PyObject結(jié)構(gòu)婆跑,這意味著此熬,我們可以通過PyObject* 去引用一個PyIntObject實(shí)例。這就實(shí)現(xiàn)了在c++中通過基類指針去引用和訪問派生類對象的多態(tài)特性滑进。更進(jìn)一步的說犀忱,所有的python對象,都包含一個PyObject頭郊供,其中存儲著這個對象本身的引用計(jì)數(shù)和所述類型峡碉。不同類型的對象可能擁有不同類型的成員近哟,這些成員會存貯在對象內(nèi)存空間中PyObject頭后面的內(nèi)存里驮审。

Python 對象可以概括的分為兩種,定長對象和非定長對象吉执。定長對象指的是這類對象的數(shù)據(jù)成員長度全部相同疯淫,反之,兩個同類非定長對象的數(shù)據(jù)成員是可以不同的戳玫。顯然熙掺,Python int對象就是一個定長python對象判族,而python string對象就是非定長對象雀瓢。因?yàn)閮蓚€python string對象的長度是不相同的咆课。對于非定長的python對象磕蒲,其內(nèi)存頭部除了保存引用計(jì)數(shù)和類型之外各薇,還保存了該對象實(shí)例本身的數(shù)據(jù)長度ob_size渡冻。

定長對象結(jié)構(gòu):

變長對象結(jié)構(gòu):

類型

Python類型在python實(shí)現(xiàn)中踊兜,用PyTypeObject 實(shí)例來描述犹赖。Python類型有很多種试浙,每一種類型董瞻,都用一個PyTypeObject實(shí)例來描述。例如整數(shù)類型用PyInt_Type描述田巴,string類型用PyString_Type描述钠糊,PyTypeObject中主要存貯了特定Python類型的方法,方法以函數(shù)指針的形式存儲在PyTypeObject的結(jié)構(gòu)里壹哺。

首先應(yīng)該注意到的是抄伍,描述python類型的數(shù)據(jù)結(jié)構(gòu)中,竟然有一個非定長python對象頭管宵,這恰恰印證了逝慧,python類型本身也是對象的說法昔脯。仔細(xì)觀察pyTypeObject數(shù)據(jù)結(jié)構(gòu),其中大部分的成員是用于保存類型特殊方法的默認(rèn)的c語言實(shí)現(xiàn)的笛臣。舉例來說tp_new,對應(yīng)于new 的實(shí)現(xiàn)云稚,tp_init 對應(yīng)于init 的實(shí)現(xiàn)。關(guān)于類型的方法沈堡,在這里暫且按下不表静陈,我們首先關(guān)注pyTypeObject中的ob_type 和tp_bases兩個成員。Ob_type 成員用于指向一個python對象的python類型诞丽。那么作為類對象鲸拥,其類型就是元類,所以僧免,一個用于描述某種python類型的pyTypeObject實(shí)例刑赶,其Ob_type 指向的是這個python類型的元類所對應(yīng)的pyTypeObject實(shí)例。默認(rèn)情況下python類型的元類是type類型懂衩,其對應(yīng)的pyTypeObject實(shí)例為 pyType_Type撞叨。 tp_bases 指向一個數(shù)組,其中存儲了python類型的直接基類浊洞。如果一個python類型直接繼承自object(對應(yīng)的pyTypeObject 實(shí)例為pyBaseObject_Type)牵敷,那么其對應(yīng)的pyTypeObject實(shí)例的tp_bases 成員中只含有一個指向pyBaseObject_Type 對象的指針。

object與type

現(xiàn)在我們已經(jīng)知道了法希,在python類型中有兩個特殊的類型枷餐,object和type。在python底層分別用pyBaseObject_Type和pyType_Type兩個pyTypeObject實(shí)例描述苫亦。

Object和type的元類都是type毛肋,這意味著,pyBaseObject_Type和pyType_Type的obtype都指向pyType_Type屋剑。

Object和type的基類都是object润匙,這意味著,pyBaseObject_Type和pyType_Type的tb_bases中都有且只有一個指向pyBaseObject_Type的指針饼丘。

python對象系統(tǒng)小結(jié)

到此趁桃,對于python對象與python類型之間的基本關(guān)系可以算是梳理清楚了,在這個基礎(chǔ)之上肄鸽,之后還可以進(jìn)一步去探索探索自定義類型的創(chuàng)建卫病,類型實(shí)例化等內(nèi)容。但是在這之前典徘,這里我們用一個示意圖描述python對象與類型之間的關(guān)系蟀苛。

部分內(nèi)置python類型對象實(shí)現(xiàn)介紹

當(dāng)我們使用python進(jìn)行開發(fā)時,最長打交道的是各種內(nèi)置類型的對象逮诲。了解常用內(nèi)置類型的對象實(shí)例在python層是如何實(shí)現(xiàn)的帜平,有助于我們更加高效的使用python幽告。這一部分,我將整理python最常用的內(nèi)置類型的對象的底層實(shí)現(xiàn)裆甩。

int

Python 底層使用PyIntObject 結(jié)構(gòu)來描述python int 對象冗锁。PyIntObject是一個定長的python對象,其構(gòu)造十分簡單嗤栓,只含有一個成員ob_val冻河,保存的就是python int 實(shí)例中的數(shù)值。

從PyIntObject數(shù)據(jù)結(jié)構(gòu)可知茉帅,Python使用一個long 類型來保存數(shù)值叨叙。從日常使用python的經(jīng)驗(yàn)可以知道,python的整數(shù)類型是不用擔(dān)心溢出問題的堪澎,然而這里如果單單只用一個long類型去保存數(shù)值的話擂错,那么是可能出現(xiàn)溢出情況的。對于這種情況python底層做了透明的類型轉(zhuǎn)換樱蛤,一旦一個pyIntObject對象包含的數(shù)值會發(fā)生溢出钮呀,那么就重新創(chuàng)建一個pyLongObject 對象去保存數(shù)值。

對于常用的python內(nèi)置對象刹悴,另一個不得不提的事情是對象緩存機(jī)制行楞。由于python處處皆對象攒暇,這意味著為python內(nèi)置對象申請內(nèi)存空間的操作將會非常頻繁土匀。為了提高性能,針對于大部分常用的python內(nèi)置類型對象形用,python底層都有相應(yīng)的緩存措施就轧。

Python整數(shù)對象有兩個層次的緩沖措施。第一個措施是針對于小整數(shù)田度。在python2.7中妒御,

范圍在[-5,257]的整數(shù)對象,一旦創(chuàng)建將永久緩存在系統(tǒng)中镇饺。具體的存放位置是一個叫做small_ints的數(shù)組乎莉。針對于普通的整數(shù)對象,python設(shè)計(jì)了內(nèi)存塊空閑鏈表緩存所有malloc出的內(nèi)存并加以管理奸笤,這大大減少了由整數(shù)對象創(chuàng)建引發(fā)malloc的幾率惋啃。

long

Python 底層使用PyLongObject 結(jié)構(gòu)去描述python的大整數(shù)對象。PyLongObject內(nèi)部用一個數(shù)組去保存數(shù)值监右,因此PyLongObject是一個邊長對象.

在python源碼的注釋中,可以看到如何用這個數(shù)組去存儲一個大整數(shù)

“ The absolute value of a number is equal to SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2*(SHIFTi)”

bool

在python中边灭,Bool對象只有兩個對象實(shí)例Py_True 和Py_False。這是兩個PyIntObject對象健盒,數(shù)據(jù)成員的值分別為1和0绒瘦。

string

Python用PyStringObject 來描述字符串對象称簿,python字符串對象也是一個變長的數(shù)據(jù)對象。除了變長對象頭惰帽,字符串對象還包含了字符數(shù)組和字符串常量的哈希值憨降,此外對象內(nèi)部還有一個state標(biāo)志位用來標(biāo)識這個對象有沒有被python的intern緩存機(jī)制處理。

同整數(shù)對象一樣该酗,python的字符串對象也有兩種緩存機(jī)制券册。分別是針對單個字符的緩沖數(shù)組,和針對于所有字符串(包含字符)的intern機(jī)制垂涯。

Python中所有長度為1的字符串(字符)烁焙,一旦被創(chuàng)建,就會被永久的緩存到系統(tǒng)中耕赘,存儲在一個名字為characters的數(shù)組里骄蝇。

而python的字符串intern機(jī)制,保證了任何兩個字符串變量操骡,如果其字符串內(nèi)容相同九火,那么兩個名字引用的是用一個字符串對象。

list

Python用PyListObject來描述列表對象册招,PyListObject的數(shù)據(jù)結(jié)構(gòu)如下圖:

從上圖可以看出岔激,PyListObject看起來是一個變長的pyhon對象,從直觀理解上是掰,列表對象是變長的這個事情也是說的通的虑鼎,但是個人在這里傾向于認(rèn)為PyListObject是定長的列表對象。列表對象維護(hù)的數(shù)據(jù)對象都存儲在由ob_item指向的空間键痛,而這段空間與PyListOBject是分離的炫彩!所以PyListObject本身應(yīng)該是定長的。Ob_size在這里用于指出列表當(dāng)前有多少個對象絮短,allocated用于描述Ob_item所指向的內(nèi)存空間的總長度江兢。當(dāng)ob_size等于allocated時意味著內(nèi)存已滿,需要重新分配一塊內(nèi)存給這個列表對象丁频,這個過程與c++ vector容器是一致的杉允。

dict

Python 用PyDictObject來描述字典對象,PyDictObject的數(shù)據(jù)結(jié)構(gòu)如下圖:

可以看到席里,PyDictObject 一個定長的python對象叔磷,注意到PyDictObject中有兩個重要的成員,Ma_table 和Ma_smalltable胁勺。Ma_table 的作用和list對象中Ob_item的作用是一樣的世澜,指向的是真正保存字典元素的空間,當(dāng)字典中的元素個數(shù)小于8時署穗,這片空間就是Ma_smalltable數(shù)組寥裂!而當(dāng)字典元素大于8時嵌洼,系統(tǒng)會另外申請新的內(nèi)存空間,并將ma_smalltable的內(nèi)容拷貝過去封恰,最后使ma_table指向新的內(nèi)存空間麻养。

PyDictObject 保存的元素是一個pyDictEntry, 顯然這是一個鍵值對,其結(jié)構(gòu)如下

可以看到的是诺舔,無論是鍵還是值鳖昌,保存在PyDictObject中的都僅僅是一個指針。

PyDictObject除了作為python層dict對象的描述低飒,在python底層的實(shí)現(xiàn)中许昨,應(yīng)用也極為廣泛。例如string intern緩存褥赊,locals 命名空間都是用PyDictOBject實(shí)現(xiàn)的糕档。因此其對于搜索的效率要求很高。為此PydictObject采用了散列表的方式存儲元素以實(shí)現(xiàn)O(1) 復(fù)雜度的查找操作拌喉,而不是像c++ map容器一樣使用紅黑樹結(jié)構(gòu)速那。PythonDict容器對象的散列,采用的是開放定址法去實(shí)現(xiàn)尿背。當(dāng)產(chǎn)生散列沖突時端仰,通過二次探測函數(shù),發(fā)現(xiàn)下一個候選位置田藐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荔烧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坞淮,更是在濱河造成了極大的恐慌茴晋,老刑警劉巖陪捷,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件回窘,死亡現(xiàn)場離奇詭異,居然都是意外死亡市袖,警方通過查閱死者的電腦和手機(jī)啡直,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苍碟,“玉大人酒觅,你說我怎么就攤上這事∥⒎澹” “怎么了舷丹?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜓肆。 經(jīng)常有香客問我颜凯,道長谋币,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任症概,我火速辦了婚禮蕾额,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彼城。我一直安慰自己诅蝶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布募壕。 她就那樣靜靜地躺著调炬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舱馅。 梳的紋絲不亂的頭發(fā)上筐眷,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音习柠,去河邊找鬼匀谣。 笑死,一個胖子當(dāng)著我的面吹牛资溃,可吹牛的內(nèi)容都是我干的武翎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼溶锭,長吁一口氣:“原來是場噩夢啊……” “哼宝恶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趴捅,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤垫毙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拱绑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體综芥,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年猎拨,在試婚紗的時候發(fā)現(xiàn)自己被綠了膀藐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡红省,死狀恐怖额各,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吧恃,我是刑警寧澤虾啦,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響傲醉,放射性物質(zhì)發(fā)生泄漏针饥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一需频、第九天 我趴在偏房一處隱蔽的房頂上張望丁眼。 院中可真熱鬧,春花似錦昭殉、人聲如沸苞七。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹂风。三九已至,卻和暖如春乾蓬,著一層夾襖步出監(jiān)牢的瞬間惠啄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工任内, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撵渡,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓死嗦,卻偏偏與公主長得像趋距,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子越除,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1.元類 1.1.1類也是對象 在大多數(shù)編程語言中节腐,類就是一組用來描述如何生成一個對象的代碼段。在Python中這...
    TENG書閱讀 1,275評論 0 3
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,005評論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理摘盆,服務(wù)發(fā)現(xiàn)翼雀,斷路器,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • 早上先生剛醒來孩擂,還一臉疲憊狼渊。說最近一直加班都沒有休息,很累肋殴,一直睡不夠囤锉。 待他洗漱過后,讓他坐下护锤,喝點(diǎn)水吃點(diǎn)水果...
    子蘭_時空閱讀 198評論 0 1
  • 這幾天在讀《把時間當(dāng)朋友》,李笑來在第三章管理中講到了任務(wù)拆解酿傍。每個人從目標(biāo)出發(fā)烙懦,然后尋求達(dá)到目標(biāo)的過程。將過程分...
    花兒朵閱讀 1,018評論 1 0