深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

在看一些框架源代碼的過(guò)程中碰到很多元類的實(shí)例,看起來(lái)很吃力很晦澀;在看python cookbook中關(guān)于元類創(chuàng)建單例模式的那一節(jié)有些疑惑。因此花了幾天時(shí)間研究下元類這個(gè)概念。通過(guò)學(xué)習(xí)元類,我對(duì)python的面向?qū)ο笥辛烁由钊氲牧私?。這里將一篇寫的非常好的文章基本照搬過(guò)來(lái)吧,這是一篇在Stack overflow上很熱的帖子,我看http://blog.jobbole.com/21351/這篇博客對(duì)其進(jìn)行了翻譯。

專注于為中小企業(yè)提供成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)定州免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了千余家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

一、理解類也是對(duì)象

在理解元類之前,你需要先掌握Python中的類。Python中類的概念借鑒于Smalltalk,這顯得有些奇特。在大多數(shù)編程語(yǔ)言中,類就是一組用來(lái)描述如何生成一個(gè)對(duì)象的代碼段。在Python中這一點(diǎn)仍然成立:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

但是,Python中的類還遠(yuǎn)不止如此。類同樣也是一種對(duì)象。只要你使用關(guān)鍵字class,Python解釋器在執(zhí)行的時(shí)候就會(huì)創(chuàng)建一個(gè)對(duì)象。下面的代碼段:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

將在內(nèi)存中創(chuàng)建一個(gè)對(duì)象,名字就是ObjectCreator。這個(gè)對(duì)象(類)自身?yè)碛袆?chuàng)建對(duì)象(類實(shí)例)的能力,而這就是為什么它是一個(gè)類的原因。但是,它的本質(zhì)仍然是一個(gè)對(duì)象,于是你可以對(duì)它做如下的操作:

你可以將它賦值給一個(gè)變量, 你可以拷貝它, 你可以為它增加屬性, 你可以將它作為函數(shù)參數(shù)進(jìn)行傳遞。

下面是示例:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

二、動(dòng)態(tài)地創(chuàng)建類

1、通過(guò)return class動(dòng)態(tài)的構(gòu)建需要的類

因?yàn)轭愐彩菍?duì)象,你可以在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建它們,就像其他任何對(duì)象一樣。首先,你可以在函數(shù)中創(chuàng)建類,使用class關(guān)鍵字即可。?

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

2、通過(guò)type函數(shù)構(gòu)造類

但這還不夠動(dòng)態(tài),因?yàn)槟闳匀恍枰约壕帉懻麄€(gè)類的代碼。由于類也是對(duì)象,所以它們必須是通過(guò)什么東西來(lái)生成的才對(duì)。當(dāng)你使用class關(guān)鍵字時(shí),Python解釋器自動(dòng)創(chuàng)建這個(gè)對(duì)象。但就和Python中的大多數(shù)事情一樣,Python仍然提供給你手動(dòng)處理的方法。還記得內(nèi)建函數(shù)type嗎?這個(gè)古老但強(qiáng)大的函數(shù)能夠讓你知道一個(gè)對(duì)象的類型是什么,就像這樣:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

這里,type有一種完全不同的能力,它也能動(dòng)態(tài)的創(chuàng)建類。type可以接受一個(gè)類的描述作為參數(shù),然后返回一個(gè)類。(我知道,根據(jù)傳入?yún)?shù)的不同,同一個(gè)函數(shù)擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向后兼容性)

type的語(yǔ)法:

type(類名, 父類的元組(針對(duì)繼承的情況,可以為空),包含屬性的字典(名稱和值))

比如下面的代碼:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

可以手動(dòng)通過(guò)type創(chuàng)建,其實(shí)

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

?你會(huì)發(fā)現(xiàn)我們使用“MyShinyClass”作為類名,并且也可以把它當(dāng)做一個(gè)變量來(lái)作為類的引用。

接下來(lái)我們通過(guò)一個(gè)具體的例子看看type是如何創(chuàng)建類的,范例:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

可以看到,在Python中,類也是對(duì)象,你可以動(dòng)態(tài)的創(chuàng)建類。這就是當(dāng)我們使用關(guān)鍵字class時(shí)Python在幕后做的事情,而這就是通過(guò)元類來(lái)實(shí)現(xiàn)的。

三、元類

1、什么是元類

通過(guò)上文的描述,我們知道了Python中的類也是對(duì)象。元類就是用來(lái)創(chuàng)建這些類(對(duì)象)的,元類就是類的類,你可以這樣理解為:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

函數(shù)type實(shí)際上是一個(gè)元類。type就是Python在背后用來(lái)創(chuàng)建所有類的元類?,F(xiàn)在你想知道那為什么type會(huì)全部采用小寫形式而不是Type呢?好吧,我猜這是為了和str保持一致性,str是用來(lái)創(chuàng)建字符串對(duì)象的類,而int是用來(lái)創(chuàng)建整數(shù)對(duì)象的類。type就是創(chuàng)建類對(duì)象的類。你可以通過(guò)檢查_(kāi)_class__屬性來(lái)看到這一點(diǎn)。Python中所有的東西,注意,我是指所有的東西——都是對(duì)象。這包括整數(shù)、字符串、函數(shù)以及類。它們?nèi)慷际菍?duì)象,而且它們都是從一個(gè)類創(chuàng)建而來(lái)。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

因此,元類就是創(chuàng)建類這種對(duì)象的東西,?type就是Python的內(nèi)建元類,當(dāng)然了,你也可以創(chuàng)建自己的元類。

2、__metaclass__屬性

你可以在寫一個(gè)類的時(shí)候?yàn)槠涮砑觃_metaclass__屬性,定義了__metaclass__就定義了這個(gè)類的元類。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

例如:當(dāng)我們寫如下代碼時(shí) :

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

在該類并定義的時(shí)候,它還沒(méi)有在內(nèi)存中生成,知道它被調(diào)用。Python做了如下的操作:

1)Foo中有__metaclass__這個(gè)屬性嗎?如果是,Python會(huì)在內(nèi)存中通過(guò)__metaclass__創(chuàng)建一個(gè)名字為Foo的類對(duì)象(我說(shuō)的是類對(duì)象,請(qǐng)緊跟我的思路)。

2)如果Python沒(méi)有找到__metaclass__,它會(huì)繼續(xù)在父類中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。

3)如果Python在任何父類中都找不到__metaclass__,它就會(huì)在模塊層次中去尋找__metaclass__,并嘗試做同樣的操作。

4)如果還是找不到__metaclass__,Python就會(huì)用內(nèi)置的type來(lái)創(chuàng)建這個(gè)類對(duì)象。

現(xiàn)在的問(wèn)題就是,你可以在__metaclass__中放置些什么代碼呢?

答案就是:可以創(chuàng)建一個(gè)類的東西。那么什么可以用來(lái)創(chuàng)建一個(gè)類呢?type,或者任何使用到type或者子類化type的東西都可以。

三、自定義元類

元類的主要目的就是為了當(dāng)創(chuàng)建類時(shí)能夠自動(dòng)地改變類。通常,你會(huì)為API做這樣的事情,你希望可以創(chuàng)建符合當(dāng)前上下文的類。假想一個(gè)很傻的例子,你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過(guò)設(shè)定__metaclass__。采用這種方法,這個(gè)模塊中的所有類都會(huì)通過(guò)這個(gè)元類來(lái)創(chuàng)建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬(wàn)事大吉了。

__metaclass__實(shí)際上可以被任意調(diào)用,它并不需要是一個(gè)正式的類。所以,我們這里就先以一個(gè)簡(jiǎn)單的函數(shù)作為例子開(kāi)始。

1、使用函數(shù)當(dāng)做元類

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

2、使用class來(lái)當(dāng)做元類

由于__metaclass__必須返回一個(gè)類。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

?但是,這種方式其實(shí)不是OOP。我們直接調(diào)用了type,而且我們沒(méi)有改寫父類的__new__方法。現(xiàn)在讓我們這樣去處理:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

你可能已經(jīng)注意到了有個(gè)額外的參數(shù)upperattr_metaclass,這并沒(méi)有什么特別的。類方法的第一個(gè)參數(shù)總是表示當(dāng)前的實(shí)例,就像在普通的類方法中的self參數(shù)一樣。當(dāng)然了,為了清晰起見(jiàn),這里的名字我起的比較長(zhǎng)。但是就像self一樣,所有的參數(shù)都有它們的傳統(tǒng)名稱。因此,在真實(shí)的產(chǎn)品代碼中一個(gè)元類應(yīng)該是像這樣的:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

如果使用super方法的話,我們還可以使它變得更清晰一些。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

四、使用原來(lái)創(chuàng)建ORM的實(shí)例?

我們通過(guò)創(chuàng)建一個(gè)類似Django中的ORM來(lái)熟悉一下元類的使用,通常元類用來(lái)創(chuàng)建API是非常好的選擇,使用元類的編寫很復(fù)雜但使用者可以非常簡(jiǎn)潔的調(diào)用API。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

例如:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

接下來(lái)我么來(lái)實(shí)現(xiàn)這么個(gè)功能:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

五、使用__new__方法和元類方式分別實(shí)現(xiàn)單例模式

1、__new__、__init__、__call__的介紹

在講到使用元類創(chuàng)建單例模式之前,比需了解__new__這個(gè)內(nèi)置方法的作用,在上面講元類的時(shí)候我們用到了__new__方法來(lái)實(shí)現(xiàn)類的創(chuàng)建。然而我在那之前還是對(duì)__new__這個(gè)方法和__init__方法有一定的疑惑。因此這里花點(diǎn)時(shí)間對(duì)其概念做一次了解和區(qū)分。

__new__方法負(fù)責(zé)創(chuàng)建一個(gè)實(shí)例對(duì)象,在對(duì)象被創(chuàng)建的時(shí)候調(diào)用該方法它是一個(gè)類方法。__new__方法在返回一個(gè)實(shí)例之后,會(huì)自動(dòng)的調(diào)用__init__方法,對(duì)實(shí)例進(jìn)行初始化。如果__new__方法不返回值,或者返回的不是實(shí)例,那么它就不會(huì)自動(dòng)的去調(diào)用__init__方法。

__init__ 方法負(fù)責(zé)將該實(shí)例對(duì)象進(jìn)行初始化,在對(duì)象被創(chuàng)建之后調(diào)用該方法,在__new__方法創(chuàng)建出一個(gè)實(shí)例后對(duì)實(shí)例屬性進(jìn)行初始化。__init__方法可以沒(méi)有返回值。

__call__方法其實(shí)和類的創(chuàng)建過(guò)程和實(shí)例化沒(méi)有多大關(guān)系了,定義了__call__方法才能被使用函數(shù)的方式執(zhí)行。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

打個(gè)比方幫助理解:如果將創(chuàng)建實(shí)例的過(guò)程比作建一個(gè)房子。

那么class就是一個(gè)房屋的設(shè)計(jì)圖,他規(guī)定了這個(gè)房子有幾個(gè)房間,每個(gè)人房間的大小朝向等。這個(gè)設(shè)計(jì)圖就是累的結(jié)構(gòu)

__new__就是一個(gè)房屋的框架,每個(gè)具體的房屋都需要先搭好框架后才能進(jìn)行專修,當(dāng)然現(xiàn)有了房屋設(shè)計(jì)才能有具體的房屋框架出來(lái)。這個(gè)就是從類到類實(shí)例的創(chuàng)建。

__init__就是裝修房子的過(guò)程,對(duì)房屋的墻面和地板等顏色材質(zhì)的豐富就是它該做的事情,當(dāng)然先有具體的房子框架出來(lái)才能進(jìn)行裝飾了。這個(gè)就是實(shí)例屬性的初始化,它是在__new__出一個(gè)實(shí)例后才能初始化。

__call__就是房子的電話,有了固定電話,才能被打電話嘛(就是通過(guò)括號(hào)的方式像函數(shù)一樣執(zhí)行)。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

?子類如果重寫__new__方法,一般依然要調(diào)用父類的__new__方法。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

?必須注意的是,類的__new__方法之后,必須生成本類的實(shí)例才能自動(dòng)調(diào)用本類的__init__方法進(jìn)行初始化,否則不會(huì)自動(dòng)調(diào)用__init__.

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

2.實(shí)現(xiàn)單例模式:

依照Python官方文檔的說(shuō)法,__new__方法主要是當(dāng)你繼承一些不可變的class時(shí)(比如int, str, tuple), 提供給你一個(gè)自定義這些類的實(shí)例化過(guò)程的途徑。還有就是實(shí)現(xiàn)自定義的metaclass。接下來(lái)我們分別通過(guò)這兩種方式來(lái)實(shí)現(xiàn)單例模式。當(dāng)初在看到cookbook中的元類來(lái)實(shí)現(xiàn)單例模式的時(shí)候?qū)ζ湎喈?dāng)疑惑,因此才有了上面這些對(duì)元類的總結(jié)。

簡(jiǎn)單來(lái)說(shuō),單例模式的原理就是通過(guò)在類屬性中添加一個(gè)單例判定位ins_flag,通過(guò)這個(gè)flag判斷是否已經(jīng)被實(shí)例化過(guò)了,如果被實(shí)例化過(guò)了就返回該實(shí)例。

1)__new__方法實(shí)現(xiàn)單例:

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

因?yàn)橹貙慱_new__方法,所以繼承至Singleton的類,在不重寫__new__的情況下都將是單例模式。

2)元類實(shí)現(xiàn)單例

當(dāng)初我也很疑惑為什么我們是從寫使用元類的__init__方法,而不是使用__new__方法來(lái)初為元類增加一個(gè)屬性。其實(shí)我只是上面那一段關(guān)于元類中__new__方法迷惑了,它主要用于我們需要對(duì)類的結(jié)構(gòu)進(jìn)行改變的時(shí)候我們才要重寫這個(gè)方法。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

基于這個(gè)例子:

我們知道元類(Singleton)生成的實(shí)例是一個(gè)類(Foo),而這里我們僅僅需要對(duì)這個(gè)實(shí)例(Foo)增加一個(gè)屬性(__instance)來(lái)判斷和保存生成的單例。想想也知道為一個(gè)類添加一個(gè)屬性當(dāng)然是在__init__中實(shí)現(xiàn)了。

關(guān)于__call__方法的調(diào)用,因?yàn)镕oo是Singleton的一個(gè)實(shí)例。所以Foo()這樣的方式就調(diào)用了Singleton的__call__方法。不明白就回頭看看上一節(jié)中的__call__方法介紹。

假如我們通過(guò)元類的__new__方法來(lái)也可以實(shí)現(xiàn),但顯然沒(méi)有通過(guò)__init__來(lái)實(shí)現(xiàn)優(yōu)雅,因?yàn)槲覀儾粫?huì)為了為實(shí)例增加一個(gè)屬性而重寫__new__方法。所以這個(gè)形式不推薦。

深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式

當(dāng)前標(biāo)題:深刻理解Python中的元類(metaclass)以及元類實(shí)現(xiàn)單例模式
文章來(lái)源:http://muchs.cn/article44/ipighe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、搜索引擎優(yōu)化、域名注冊(cè)、網(wǎng)站內(nèi)鏈、App開(kāi)發(fā)定制開(kāi)發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)