一 什麼是元類 一切源自於一句話:python中一切皆為對象。讓我們先定義一個類,然後逐步分析 所有的對象都是實例化或者說調用類而得到的(調用類的過程稱為類的實例化),比如對象t1是調用類OldboyTeacher得到的。 如果一切皆為對象,那麼類OldboyTeacher本質也是一個對象,既然所有 ...
一 什麼是元類
一切源自於一句話:python中一切皆為對象。讓我們先定義一個類,然後逐步分析
class OldboyTeacher(object): school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name)
t1=OldboyTeacher('egon',18) print(type(t1)) #查看對象t1的類是<class '__main__.OldboyTeacher'>
所有的對象都是實例化或者說調用類而得到的(調用類的過程稱為類的實例化),比如對象t1是調用類OldboyTeacher得到的。
如果一切皆為對象,那麼類OldboyTeacher本質也是一個對象,既然所有的對象都是調用類得到的,那麼OldboyTeacher必然也是調用了一個類得到的,這個類稱為元類
print(type(OldboyTeacher)) # 結果為<class 'type'>,證明是調用了type這個元類而產生的OldboyTeacher,即預設的元類為type
二 class關鍵字創建類的流程分析
我們用class關鍵字定義的類本身也是一個對象,負責產生該對象的類稱之為元類(元類可以簡稱為類的類),內置的元類為type
class關鍵字在幫我們創建類時,必然幫我們調用了元類OldboyTeacher=type(...),那調用type時傳入的參數是什麼呢?必然是類的關鍵組成部分,一個類有三大組成部分,分別是
1、類名class_name='OldboyTeacher'
2、基類們class_bases=(object,)
3、類的名稱空間class_dic,類的名稱空間是執行類體代碼而得到的
調用type時會依次傳入以上三個參數
#exec:三個參數 #參數一:包含一系列python代碼的字元串 #參數二:全局作用域(字典形式),如果不指定,預設為globals() #參數三:局部作用域(字典形式),如果不指定,預設為locals() #可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中 g={ 'x':1, 'y':2 } l={} exec(''' global x,z x=100 z=200 m=300 ''',g,l) print(g) #{'x': 100, 'y': 2,'z':200,......} print(l) #{'m': 300}exec模擬實現創建類
三 自定義元類控制類OldboyTeacher的創建和調用
class Mymeta(type): # 只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義類
# def __init__(self,*args,**kwargs ): # 簡寫 # super(Mymeta, self).__init__(*args,**kwargs)
# type的__call__會調用Mymeta的__init__和type代碼中的__new__創建OldboyTeacher對象(如果__new__沒有重寫) def __init__(self, class_name, class_bases, class_dic): # class OldboyTeacher(object, metaclass=Mymeta)時觸發 # print(self) #<class '__main__.OldboyTeacher'> # print(class_bases) #(<class 'object'>,) # print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', \ # '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>} super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父類的功能 if class_name.islower(): raise TypeError('類名%s請修改為駝峰體' % class_name) if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0: raise TypeError('類中必須有文檔註釋,並且文檔註釋不能為空') def __call__(cls, *args, **kwargs): # cls=<class '__main__.OldboyTeacher'> OldboyTeacher()的時候觸發執行 # 1、調用__new__產生一個空對象obj obj = cls.__new__(cls) # 此處的cls是類OldoyTeacher,必須傳參,代表創建一個OldboyTeacher的對象obj,
# 2、調用__init__初始化空對象obj cls.__init__(obj, *args, **kwargs) # 3、返回初始化好的對象obj 就是OldboyTeacher()的實例化對象 old_tea return obj class OldboyTeacher(object, metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...}) """ 類OldboyTeacher的文檔註釋 """ school = 'oldboy' def __init__(self, name, age): self.name = name self.age = age def say(self): print('%s says welcome to the oldboy to learn Python' % self.name) old_tea = OldboyTeacher(‘cp’,19)
簡單來說:(type指元類,預設就是type)
- 類的創建 (class 類 )
type.__init__
- 對象的創建 ( 類() )
type.__call__
- 類.__new__
- 類.__init__
如果一類自己或基類中指定了metaclass,那麼該類就是由metaclass指定的type或mytype創建。
1 class MyType(type): 2 def __init__(self,*args,**kwargs): 3 print('111') 4 super(MyType,self).__init__(*args,**kwargs) 5 6 7 class Base(object, metaclass=MyType): 8 pass 9 10 class Foo(Base): 11 pass 12 13 # 列印兩次111父類指定metaclass
1 class MyType(type): 2 def __init__(self,*args,**kwargs): 3 print('111') 4 super(MyType,self).__init__(*args,**kwargs) 5 6 7 # class Base(object, metaclass=MyType): 8 # pass 9 10 Base = MyType('Base',(object,),{}) 11 12 class Foo(Base): 13 pass 14 15 # ============================ 16 17 class MyType(type): 18 def __init__(self,*args,**kwargs): 19 print('111') 20 super(MyType,self).__init__(*args,**kwargs) 21 22 23 def with_metaclass(arg): 24 Base = MyType('Base',(arg,),{}) 25 return Base 26 27 class Foo(with_metaclass(object)): 28 pass兩種變形的metaclass指定方法
四 自定義元類的類屬性查找
我們用class自定義的類也全都是對象(包括object類本身也是元類type的 一個實例,可以用type(object)查看),我們學習過繼承的實現原理,如果把類當成對象去看,將下述繼承應該說成是:對象OldboyTeacher繼承對象Foo,對象Foo繼承對象Bar,對象Bar繼承對象object。
class Mymeta(type): n=444 def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Bar(object): n=333 class Foo(Bar): n=222 class OldboyTeacher(Foo,metaclass=Mymeta): n=111 school='oldboy' def __init__(self,name,age): self.name=name
def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) print(OldboyTeacher.n) #自下而上依次註釋各個類中的n=xxx,然後重新運行程式,發現n的查找順序為OldboyTeacher->Foo->Bar->object->Mymeta->type
於是屬性查找應該分成兩層,一層是對象層(基於c3演算法的MRO)的查找,另外一個層則是類層(即元類層)的查找。
#查找順序: #1、先對象層:OldoyTeacher->Foo->Bar->object #2、然後元類層:Mymeta->type
class Mymeta(type): n=444 def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> obj=self.__new__(self) print(self.__new__ is object.__new__) #True class Bar(object): n=333 # def __new__(cls, *args, **kwargs): # print('Bar.__new__') class Foo(Bar): n=222 # def __new__(cls, *args, **kwargs): # print('Foo.__new__') class OldboyTeacher(Foo,metaclass=Mymeta): n=111 school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) # def __new__(cls, *args, **kwargs): # print('OldboyTeacher.__new__') OldboyTeacher('egon',18) #觸發OldboyTeacher的類中的__call__方法的執行,進而執行self.__new__開始查找分析下元類Mymeta中__call__里的self.__new__的查找
總結,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都沒有找到__new__的情況下,會去找object里的__new__,而object下預設就有一個__new__,所以即便是之前的類均未實現__new__,也一定會在object中找到一個,根本不會、也根本沒必要再去找元類Mymeta->type中查找__new__ 我們在元類的__call__中也可以用object.__new__(self)去造對象 但我們還是推薦在__call__中使用self.__new__(self)去創造空對象,因為這種方式會檢索三個類OldboyTeacher->Foo->Bar,而object.__new__則是直接跨過了他們三個答案
五 案例
元類屬於python最深層的語言特性,一般開發中很少用到,下麵舉一些用到元類的案例。
# 自定義Form表單 class RegisterForm(Form): name = simple.StringField( label='用戶名', validators=[ validators.DataRequired() ], widget=widgets.TextInput(), render_kw={'class': 'form-control'}, # default='alex' ) # Form指定了元類,Form其子類也會繼承指定的元類。 class Form(with_metaclass(FormMeta, BaseForm)): Meta = DefaultMeta # FormMeta源碼 class FormMeta(type): def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) # 先重用父類的初始化方法,防止需要用到父類的功能 cls._unbound_fields = None cls._wtforms_meta = None # 該源碼詳析請參見另一篇博文wtforms中Form
參考:http://www.cnblogs.com/linhaifeng/articles/8029564.html