python當中__metaclass__探討

發表於2016-10-08

最初博主是希望在python當中建立一個單列模式的類,因為python當中不像java和php當中有許可權修飾符(private),所以實現起來要繞一點。

網上找了一下python實現單列模式,類似的大概有這種方法:

這就是單列的元類,我把它小寫了,因為type也是小寫的。然後呢,在即將要實現單列的class當中這樣寫:

這樣每次 建立一個 Earth()取得的始終都應該是一個例項。

關於__metaclass__ 和type這個東西可以參考深入理解Python中的元類(metaclass)。這篇文章解決了我大部分的疑惑,但是我還是沒有搞清楚的是:

當__metaclass__是一個類的時候,metaclass是怎樣去建立一個類的?

在這之前首先說明一下:

一。python當中一切都是物件,而物件都是由類建立,這裡為了區分概念,我們不妨換一種說法:例項都是由模板建立的。

二。那麼什麼又是物件的type呢?type就是型別的意思。如果您對java稍微有一點了解。你會有這樣的認識:

由於python是一門動態語言,所以在寫程式碼的時候不必宣告變數的型別,也不可能完全確定這個變數是什麼型別,除非對自己程式碼的邏輯以及流程非常清楚,另外python在執行當中變數的型別是可以更改的。但是不能確定變數的型別,不代表就沒有型別啊。python當中的變數一樣是有型別的。那麼怎麼來看變數的型別呢?答案是使用type。

上面段程式碼分別用type檢視到了各個變數的型別。根據(一)【python當中一切都是物件,而物件都是由類建立,這裡為了區分概念,我們不妨換一種說法:例項都是由模板建立的】我們可不可以這樣說呢:language是str的例項,str是language例項的模板。因此type(a_var)的輸出就是a_var這個例項的模板。所以我們看到 type(a)的輸出是,也就是說 a例項的模板是A。

class A的模板是什麼呢?

也就是說,一個類的模板的type,類是type的一個例項,tpye例項化了一個物件,這個物件就是class。所以在python的世界裡,一切都是物件,類也是物件。

那麼有意思的地方來了,type的模板是什麼呢,也就是,type的type是什麼呢?

是不是很有意思,type的type是type。很繞,換成大白話說:type的模板是type自己。那麼是不是就可以這樣說呢?TYPE(type,為了區分說明,故意大寫)是type的模板,type是TYPE的例項,因此說明type是一個例項;而TYPE是一個模板,也就是一個類!,因為TYPE==type,那麼可以得出結論:

python當中一切都是物件,類也是物件,對於type來說,更為特殊,因為type的模板是type,也就是說,type自己建立了自己,type是自身的例項。

三。例項是由類别範本建立(也就是我們平時所寫的class),而類是由元類别範本建立(就是__metaclass__指定的類)。所以【元類和類的關係】就類似於【例項和類的關係】。

根據博主所探討的結果表明,__metaclass__在建立類的過程大概是這樣的:當類Earth的例項 earth正要被建立的時候,

  1. 查詢Earth當中是否有__metaclass__,如果沒有查詢父類是否有__metaclass__,如果沒有找到,就看包當中是否有__metaclass__,如果還是沒有,那直接使用type建立該類。如果找到了,就用該__metaclass__來建立類。
  2. 那麼如果找了__metaclass__,那麼系統首先建立一個__metaclass__的例項,而這個由metaclass建立的例項正好的一個 Earth類,注意是Earth類(class),而不是一個Earth的一個例項哦。

那麼到這一步究竟發生了些什麼呢?我們寫幾行程式碼來看一看:

最後執行的結果是這樣的:

通過這個小例子我們看到:我們並沒有使用過 Earth類,也沒有使用過SimpleMetaClass這個元類,但實際的結果看來,SimpleMetaClass這個模板確被使用過了,因為列印出了①,後面我們會知道,列印出①是因為python使用SimpleMetaClass模板來建立出了Earth這個類物件(不是Earth的一個例項)。這個過程我們可以用我們平常經常說的一句話來描述:這個步驟相當於例項化了一個metaclass(SimpleMetaClass)物件,而這個物件正好是Earth類。

那麼這裡肯定會有人問了:我平時寫class的時候都是不帶__metaclass__的啊?那是因為如果你不寫__metaclass__,最終這個類的模板就是type。上面的程式碼可以看到SimpleMetaClass是繼承自type的。

四。Earth類已經被metaclass所建立出來了,那麼當例項化一個Earth類(也就是建立一個earth物件)的時候又發生了什麼呢?

在說明這個問題之前,我們得先聊一聊__call__,__new__這兩個特殊方法。對於一個實現了__call__的類,那麼它的例項可以當做函式來呼叫。來看個例子:

而__new__有攔截類例項化的功能,在建立一個物件的過程中,執行__init__方法時,直譯器已經為物件分配了記憶體,例項已經存在了,__init__方法只是改變這個類當中的某些引數。而在執行__new__方法時,這個例項是不存在的,而__new__就是要建立這個例項,所以__new__必須要有返回值。

現在我們回過頭來想一想:為什麼建立 一個類的例項是這種寫法:

回答這個問題,我們可以用元類來解釋。我們知道類是元類的一個物件,而元類的例項都有一個__call__方法。擁有__call__方法的物件可以把物件當做一個函式呼叫。所以嘍,我們在建立一個類的例項的時候,實際上是呼叫了類物件的__call__(MetaClass:__call__)這個方法。

來看一個比較長的例子:

不知道大眾喜歡在程式碼中寫註釋的方式來講解,還是直接寫文字過程。我就寫文字過程吧。

最終上面這段程式碼執行的結果是:

我們來慢慢分析。

  1. 首先python建立SimpleMetaClass類,這個SimpleMetaClass是元類,應該是由type建立的。
  2. 當建立Earth這個類時,找到了它類中有__metaclass__屬性,於是,採用SimpleClass來建立這個類
  3. 建立Earh類時,直譯器會把類名,父類元祖,類的屬性三個引數傳給SimpleMetaClass
  4. SimpleMetaClass 根據 clazzName,(parent2,parent1,..),{‘attribute’:….,’method’:”}在自己__new__方法中建立出這個Earth例項【列印出①】,然後呼叫自己的__init__方法初始化類的引數【列印出②】。這時,這個Earth類作為metaclass的一個例項就被建立好了。
  5. 接下來通過 earth = Earth(9.8,R=65535) 建立一個Earth物件例項earth。這一步實際上是呼叫 Earth這個類物件的__call__(SimpleMetaClass::__call__)方法來建立一個Earth的例項。【列印出③,我們還能看到呼叫__call__的引數】。
  6. 而建立earth例項的方法__new__(Earth::__new__),和__init__(Earth:__init__),將會在Earth例項中的__call__(SimpleMetaClass::__call__)當中先後得以執行【先後列印出④⑤⑥】執行完成Earth例項earth物件被返回。
  7. 我想⑦⑧大家應該很容易理解了。

以上就是我對元類的理解,其中如有錯誤的地方還請大家斧正。

相關文章