最初博主是希望在python當中建立一個單列模式的類,因為python當中不像java和php當中有許可權修飾符(private),所以實現起來要繞一點。
網上找了一下python實現單列模式,類似的大概有這種方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class singleton(type): """ 實現單列模式的元類 總之,metaclass的主要任務是: 攔截類, 修改類, 返回類 """ def __init__(cls,classname,parrentstuple,attrdict): """ """ super(SigleInstance,cls).__init__(classname,parrentstuple,attrdict) cls._instance = None def __call__(self,*args,**kargs): """ """ if self._instance: return self._instance else: self._instance = super(SigleInstance,self).__call__(*args,**kargs) return self._instance |
這就是單列的元類,我把它小寫了,因為type也是小寫的。然後呢,在即將要實現單列的class當中這樣寫:
1 2 3 4 |
class Earth(object): __metaclass__ = singleton def __init__(self,a,b): pass |
這樣每次 建立一個 Earth()取得的始終都應該是一個例項。
關於__metaclass__ 和type這個東西可以參考深入理解Python中的元類(metaclass)。這篇文章解決了我大部分的疑惑,但是我還是沒有搞清楚的是:
當__metaclass__是一個類的時候,metaclass是怎樣去建立一個類的?
在這之前首先說明一下:
一。python當中一切都是物件,而物件都是由類建立,這裡為了區分概念,我們不妨換一種說法:例項都是由模板建立的。
二。那麼什麼又是物件的type呢?type就是型別的意思。如果您對java稍微有一點了解。你會有這樣的認識:
1 2 3 4 5 |
/** * language是一個String型別的變數,值為"python" * 在java當中,如果 Integer language = "python"就是錯誤的 */ String language = "python"; |
由於python是一門動態語言,所以在寫程式碼的時候不必宣告變數的型別,也不可能完全確定這個變數是什麼型別,除非對自己程式碼的邏輯以及流程非常清楚,另外python在執行當中變數的型別是可以更改的。但是不能確定變數的型別,不代表就沒有型別啊。python當中的變數一樣是有型別的。那麼怎麼來看變數的型別呢?答案是使用type。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
language = "python" print type(language) # python2.7中輸出:<type 'str'> # ipython 輸出 str number = 2 print type(number) #輸出:<type 'int'> class A(object): pass a = A() print type(a) #輸出:<type '__main__.A'> |
上面段程式碼分別用type檢視到了各個變數的型別。根據(一)【python當中一切都是物件,而物件都是由類建立,這裡為了區分概念,我們不妨換一種說法:例項都是由模板建立的】我們可不可以這樣說呢:language是str的例項,str是language例項的模板。因此type(a_var)的輸出就是a_var這個例項的模板。所以我們看到 type(a)的輸出是,也就是說 a例項的模板是A。
class A的模板是什麼呢?
1 2 |
print type(A) #輸出:<type 'type'> |
也就是說,一個類的模板的type,類是type的一個例項,tpye例項化了一個物件,這個物件就是class。所以在python的世界裡,一切都是物件,類也是物件。
那麼有意思的地方來了,type的模板是什麼呢,也就是,type的type是什麼呢?
1 2 |
print type(type) # 輸出<type 'type'> |
是不是很有意思,type的type是type。很繞,換成大白話說:type的模板是type自己。那麼是不是就可以這樣說呢?TYPE(type,為了區分說明,故意大寫)是type的模板,type是TYPE的例項,因此說明type是一個例項;而TYPE是一個模板,也就是一個類!,因為TYPE==type,那麼可以得出結論:
1 |
type是一個類(class),type也是自身的例項(instance) |
python當中一切都是物件,類也是物件,對於type來說,更為特殊,因為type的模板是type,也就是說,type自己建立了自己,type是自身的例項。
三。例項是由類别範本建立(也就是我們平時所寫的class),而類是由元類别範本建立(就是__metaclass__指定的類)。所以【元類和類的關係】就類似於【例項和類的關係】。
根據博主所探討的結果表明,__metaclass__在建立類的過程大概是這樣的:當類Earth的例項 earth正要被建立的時候,
- 查詢Earth當中是否有__metaclass__,如果沒有查詢父類是否有__metaclass__,如果沒有找到,就看包當中是否有__metaclass__,如果還是沒有,那直接使用type建立該類。如果找到了,就用該__metaclass__來建立類。
- 那麼如果找了__metaclass__,那麼系統首先建立一個__metaclass__的例項,而這個由metaclass建立的例項正好的一個 Earth類,注意是Earth類(class),而不是一個Earth的一個例項哦。
那麼到這一步究竟發生了些什麼呢?我們寫幾行程式碼來看一看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/usr/bin/env python #-*-coding:utf-8-*- # author : "qiulimao" # email : "qiulimao@getqiu.com" """ the module's duty """ #---------- code begins below ------- class SimpleMetaClass(type): def __init__(self,*args,**kwargs): print "you have create a class instance by metaclass" super(SimpleMetaClass,self).__init__(*args,**kwargs) class Earth(object): __metaclass__ = SimpleMetaClass def sayHello(): print "hello world" if __name__ == "__main__": print "do something that have nothing with SimpleMetaClass and Earth" |
最後執行的結果是這樣的:
1 2 3 |
you have create a class instance by metaclass #① do something that have nothing with SimpleMetaClass and Earth #② |
通過這個小例子我們看到:我們並沒有使用過 Earth類,也沒有使用過SimpleMetaClass這個元類,但實際的結果看來,SimpleMetaClass這個模板確被使用過了,因為列印出了①,後面我們會知道,列印出①是因為python使用SimpleMetaClass模板來建立出了Earth這個類物件(不是Earth的一個例項)。這個過程我們可以用我們平常經常說的一句話來描述:這個步驟相當於例項化了一個metaclass(SimpleMetaClass)物件,而這個物件正好是Earth類。
那麼這裡肯定會有人問了:我平時寫class的時候都是不帶__metaclass__的啊?那是因為如果你不寫__metaclass__,最終這個類的模板就是type。上面的程式碼可以看到SimpleMetaClass是繼承自type的。
四。Earth類已經被metaclass所建立出來了,那麼當例項化一個Earth類(也就是建立一個earth物件)的時候又發生了什麼呢?
在說明這個問題之前,我們得先聊一聊__call__,__new__這兩個特殊方法。對於一個實現了__call__的類,那麼它的例項可以當做函式來呼叫。來看個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MagicCall(object): def __new__(cls,name): return super(MagicCall,cls).__new__(cls) def __init__(self,name): self.name=name def __call__(self): print "you have invoked __call__ method...." if __name__ == '__main__': magicCall = MagicCall("python") magicCall() #輸出的結果為:you have invoked __call__ method.... |
而__new__有攔截類例項化的功能,在建立一個物件的過程中,執行__init__方法時,直譯器已經為物件分配了記憶體,例項已經存在了,__init__方法只是改變這個類當中的某些引數。而在執行__new__方法時,這個例項是不存在的,而__new__就是要建立這個例項,所以__new__必須要有返回值。
現在我們回過頭來想一想:為什麼建立 一個類的例項是這種寫法:
1 2 |
instance = SomeClass() instance = SomeClass(args1,args2,...) |
回答這個問題,我們可以用元類來解釋。我們知道類是元類的一個物件,而元類的例項都有一個__call__方法。擁有__call__方法的物件可以把物件當做一個函式呼叫。所以嘍,我們在建立一個類的例項的時候,實際上是呼叫了類物件的__call__(MetaClass:__call__)這個方法。
來看一個比較長的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#!/usr/bin/env python #-*-coding:utf-8-*- # author : "qiulimao" # email : "qiulimao@getqiu.com" """ the module's duty """ #---------- code begins below ------- class SimpleMetaClass(type): def __new__(cls,*args,**kwargs): print "creating class Earth..." return super(SimpleMetaClass,cls).__new__(cls,*args,**kwargs) def __init__(self,*args,**kwargs): print "you have create a class instance by metaclass" super(SimpleMetaClass,self).__init__(*args,**kwargs) def __call__(self,*args,**kwargs): print "__call__ in metaclass has been invoked...","the args:",args,kwargs return super(SimpleMetaClass,self).__call__(*args,**kwargs) class Earth(object): __metaclass__ = SimpleMetaClass def __new__(cls,g,R=65535): print "creating instance using __new__" cls.g = g cls.R = R return super(Earth,cls).__new__(cls); def __init__(self,g,R=65535): print "initializing instance in __init__" print "gravity on Earth is:%f" % self.g def __call__(self): print self.g def sayHello(self): print "hello earth,your gravity is:%f" % self.g if __name__ == "__main__": earth = Earth(9.8,R=65535) earth() earth.sayHello() |
不知道大眾喜歡在程式碼中寫註釋的方式來講解,還是直接寫文字過程。我就寫文字過程吧。
最終上面這段程式碼執行的結果是:
1 2 3 4 5 6 7 8 9 |
①creating class Earth... ②you have create a class instance by metaclass ③__call__ in metaclass has been invoked... the args: (9.8,) {'R': 65535} ④creating instance using __new__ ⑤initializing instance in __init__ ⑥gravity on Earth is:9.800000 ⑦9.8 ⑧hello earth,your gravity is:9.800000 |
我們來慢慢分析。
- 首先python建立SimpleMetaClass類,這個SimpleMetaClass是元類,應該是由type建立的。
- 當建立Earth這個類時,找到了它類中有__metaclass__屬性,於是,採用SimpleClass來建立這個類
- 建立Earh類時,直譯器會把類名,父類元祖,類的屬性三個引數傳給SimpleMetaClass
- SimpleMetaClass 根據 clazzName,(parent2,parent1,..),{‘attribute’:….,’method’:”}在自己__new__方法中建立出這個Earth例項【列印出①】,然後呼叫自己的__init__方法初始化類的引數【列印出②】。這時,這個Earth類作為metaclass的一個例項就被建立好了。
- 接下來通過 earth = Earth(9.8,R=65535) 建立一個Earth物件例項earth。這一步實際上是呼叫 Earth這個類物件的__call__(SimpleMetaClass::__call__)方法來建立一個Earth的例項。【列印出③,我們還能看到呼叫__call__的引數】。
- 而建立earth例項的方法__new__(Earth::__new__),和__init__(Earth:__init__),將會在Earth例項中的__call__(SimpleMetaClass::__call__)當中先後得以執行【先後列印出④⑤⑥】執行完成Earth例項earth物件被返回。
- 我想⑦⑧大家應該很容易理解了。
以上就是我對元類的理解,其中如有錯誤的地方還請大家斧正。