python學習筆記——元類

寫在前面

這兩天仔細研究了python中元類的概念赡鲜,從最開始的一頭霧水桑逝,到現(xiàn)在的漸漸有一點明白赴背。想借這篇文章來闡述一下我對于python中元類的一些粗淺見解毛雇,同時也希望能給其他人一些啟發(fā)烹困,共同進步玄妈。首先,我想說在理解元類時一定要在大腦中時刻按照OOP的編程理念對程序進行分析才能夠理解元類中一些比較難以理解的地方髓梅,這也是筆者學習元類的一個小經驗拟蜻。

什么是元類

元類在python中最簡單的定義就是——type的子類。為了弄清楚這個模糊的定義枯饿,首先要弄清楚的是什么是type類酝锅,這對于后面的理解是非常重要的。type顧名思義就是“類型”的意思奢方,它和python標準庫中的其他類一樣也是一個類搔扁,但是它也有非常特殊的地方爸舒。其他的類的實例就是一個普通的實例,而類型的實例是一個類稿蹲。這是python3.0中一個更新的點扭勉,在2.X版本的python中類型的實例還不完全是一個類,在某些場景下它的實例也是一個普通的實例苛聘,不過今天我們要談的不是2.X版本中的type涂炎。讓我們用幾句簡單的代碼來驗證一下:



在代碼中首先構造了一個list實例,然后用type的構造函數(shù)以list的實例為參數(shù)構造了一個type的實例设哗,打印后我們發(fā)現(xiàn)type的實例是一個類唱捣,這和list實例的類(l.__class__)是相同的。這就印證了我們的一個論斷——類型的實例就是類熬拒。

熟悉OOP的人特別是C++的人都應該了解爷光,類只是聲明而不是實例垫竞,只有在程序中實例化一個類才會分配內存澎粟。但是python中類也是一個叫做類對象的對象,它不是憑空出現(xiàn)在程序中的欢瞪,它和實例一樣也需要有代碼對類對象進行實例化活烙,說穿了類對象應該也是一種特殊的實例,為什么這么說呢遣鼓?圖一中的代碼應該能給我們些許暗示:通過type的構造函數(shù)構造出類型的實例是一個類啸盏,或者說類對象(不能再細說了,再說就繞進去了骑祟,讀者自己意會吧)

那么回懦,寫到這里我們就可以說type類負責類對象的創(chuàng)建,一般情況下這種創(chuàng)建是隱式的不被我們發(fā)覺的次企。而如果我們要顯式地觀察這個過程就可以通過創(chuàng)建元類來實現(xiàn)怯晕。在OOP中攔截一個類方法的方式之一就是繼承這個類并且重載需要攔截的方法,那么這里就可以引出最開始給出的元類定義缸棵,元類就是type的子類舟茶。元類通過繼承type類,進而重載type類中的一些方法來達到控制類對象生成的目的堵第,這是元類編程中一個大體的思想吧凉。

類對象的創(chuàng)建過程

類對象的創(chuàng)建過程和實例的創(chuàng)建過程相似或者說大體上是一致的,我們通過一個例子來了解吧踏志。



打印效果如下:



可見在類對象創(chuàng)建時首先調用元類中的__new__方法阀捅,然后調用__init__方法。其中__new__方法返回類對象的實例针余,__init__方法對類對象進行一些初始化也搓。這兩個方法都是type類中的方法赏廓,在這里我們繼承type類后重載這兩個方法等于覆蓋了type類中的這兩個方法。有了這個直觀的感受我們接著進一步探索類對象的創(chuàng)建過程傍妒。

一個類在聲明了元類之后(就是類名后面加個括號幔摸,里面寫著metaclass=XXX)。當程序運行時颤练,在class語句的末尾就會自動創(chuàng)建類對象既忆。假設我們有一個demo類,聲明它的元類為Meta嗦玖,那么在demo的class語句完結后緊接著執(zhí)行一句:

demo=Meta(name,bases,dict)

傳入三個參數(shù)患雇,第一個是demo的類名稱(字符串類型),第二個是demo類的父類元組宇挫,第三個是demo類的類字典苛吱。這時候就需要關注Meta了,Meta也是一個類器瘪,它是type的子類翠储,同時type也是Meta的元類,就是說Meta類對象是type的實例橡疼。在type類中有一個__call__方法援所,這個方法是一個運算符重載方法,攔截type(xxx,xxx,xxx,...)這樣的調用欣除∽∈茫回到剛才的

demo=Meta(name,bases,dict)

由于Meta是type的實例,因此當這樣的調用形式出現(xiàn)時必然會觸發(fā)type中的__call__方法历帚。由于Meta是type的實例滔岳,因此在傳參的時候除了剛剛寫出的三個參數(shù)外還會自動傳入一個Meta自己,因此type的__call__方法實際上會接收到四個參數(shù)⊥炖危現(xiàn)在程序運行到了type的__call__方法中谱煤,在這個方法中的調用過程我們用這樣的一段偽代碼來展示:



正如同代碼中所展示的,在__call__方法中首先調用元類的__new__方法得到一個類對象卓研,再把這個類對象傳入元類的__init__方法對這個類對象進行初始化最后再返回這個類對象趴俘,這也印證了最初我們的論斷。元類構造一個類對象基本上就是這么一個過程奏赘。

一個例子

為了更加說明元類構造類對象的過程寥闪,我從書上找了一個例子改了一下貼在這:



這個例子是為了說明元類構建類對象時__call__方法的調用。首先梳理一下程序的結構:Eggs和Spam是兩個常規(guī)的類磨淌,其中Spam是Eggs的子類疲憋,Spam的元類為SubMeta,而SubMeta的元類又為SuperMeta梁只。之所以要繞這么一下就是為了讓元類本身的構造過程也暴露出來缚柳。

接下來分析一下程序的運行埃脏。首先要構建Spam就要先構建SubMeta。SubMeta的元類為SuperMeta秋忙,在SuperMeta中定義了__init__和__new__方法彩掐,這是定義元類中一個比較常規(guī)的做法就不說了,我們要關注一下SuperMeta中定義的__call__方法并關注這個方法執(zhí)行的時機灰追,這很重要堵幽。首先,構建SubMeta等于執(zhí)行這樣的語句:

SubMeta=SuperMeta(name,bases,dict)

但這是否意味著SuperMeta的__call__會執(zhí)行呢弹澎?答案是否定的朴下,因為從原理上來說SuperMeta是type的實例,因此上面的調用會執(zhí)行type中的__call__方法而不是SuperMeta中的苦蒿。接著殴胧,由于SuperMeta中重載了type的__new__和__init__方法,因此type類中的__call__會調用SuperMeta的這兩個方法佩迟,調用之后SubMeta類對象就構建完成了团滥,注意此時的SubMeta是SuperMeta的類實例,明白這點很重要音五。接下來就要構建Spam類對象:

Spam=SubMeta(name,bases,dict)

由于SubMeta是SuperMeta的實例惫撰,因此上面代碼的調用會觸發(fā)SuperMeta的__call__方法羔沙,就是我們剛剛提到的那個躺涝。接著會調用SubMeta類中的__init__和__new__方法,如果SubMeta中這兩個方法找不到或者沒找全扼雏,程序就會順著繼承樹找type類中的對應方法坚嗜。

那么我們預測一下輸出吧,首先肯定是SuperMeta的__new__和__init__執(zhí)行诗充,然后是SuperMeta的__call__執(zhí)行苍蔬,接著是SubMeta的__new__和__init__執(zhí)行:



結果很顯然,印證了之前的預測蝴蜓。

在這里碟绑,我們還可以繼續(xù)思考一下。此處的Spam是SubMeta的實例茎匠,如果在SubMeta中定義一個__call__方法格仲,那么當Spam正常創(chuàng)建實例的時候會發(fā)生什么呢?這個問題就暗示了python創(chuàng)建實例背后的故事诵冒,其實這個過程和類對象的構建過程非常相似甚至代碼都是一模一樣的凯肋,同樣也是觸發(fā)__call__方法,進而調用__new__分配內存汽馋,然后調用__init__做一些初始化的工作侮东。只不過和類對象不同的是類對象創(chuàng)建中__new__返回的是一個類圈盔,而實例創(chuàng)建中返回的是一個實例。之所以會有這樣的區(qū)別在于在__call__方法中__new__和__init__的調用是取決于__call__的第一個參數(shù)悄雅,上面的例子中即為Spam驱敲,而Spam不是type的子類,因此Spam的__init__和__new__方法指向的是他自己或者是object的對應方法宽闲,因此才出現(xiàn)了__new__方法返回結果不同的現(xiàn)象癌佩。

寫在最后

好啦,元類基本的東西差不多就是這樣了便锨,至于具體應用還是蠻多的比如給類動態(tài)添加方法围辙,模擬實現(xiàn)java中接口的特性等等。這都需要自己慢慢探索實踐放案。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末姚建,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吱殉,更是在濱河造成了極大的恐慌掸冤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件友雳,死亡現(xiàn)場離奇詭異稿湿,居然都是意外死亡,警方通過查閱死者的電腦和手機押赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門饺藤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人流礁,你說我怎么就攤上這事涕俗。” “怎么了神帅?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵再姑,是天一觀的道長。 經常有香客問我找御,道長元镀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任霎桅,我火速辦了婚禮栖疑,結果婚禮上,老公的妹妹穿的比我還像新娘哆档。我一直安慰自己蔽挠,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著澳淑,像睡著了一般比原。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杠巡,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天量窘,我揣著相機與錄音,去河邊找鬼氢拥。 笑死蚌铜,一個胖子當著我的面吹牛,可吹牛的內容都是我干的嫩海。 我是一名探鬼主播冬殃,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叁怪!你這毒婦竟也來了审葬?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤奕谭,失蹤者是張志新(化名)和其女友劉穎涣觉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體血柳,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡官册,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了难捌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膝宁。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栖榨,靈堂內的尸體忽然破棺而出昆汹,到底是詐尸還是另有隱情明刷,我是刑警寧澤婴栽,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站辈末,受9級特大地震影響愚争,放射性物質發(fā)生泄漏。R本人自食惡果不足惜挤聘,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一轰枝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧组去,春花似錦鞍陨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缭裆。三九已至,卻和暖如春寿烟,著一層夾襖步出監(jiān)牢的瞬間澈驼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工筛武, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缝其,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓徘六,卻偏偏與公主長得像内边,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子待锈,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理假残,服務發(fā)現(xiàn),斷路器炉擅,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 元類允許我們攔截并擴展類創(chuàng)建----提供了一個API以插入在一條class語句結束時運行的二維邏輯辉懒,盡管是以與裝飾...
    低吟淺唱1990閱讀 169評論 0 0
  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • 1. Java基礎部分 基礎部分的順序:基本語法谍失,類相關的語法眶俩,內部類的語法,繼承相關的語法快鱼,異常的語法颠印,線程的語...
    子非魚_t_閱讀 31,581評論 18 399