目錄python
元類屬於python面向對象編程的深層魔法,99%的人都不得要領,一些自覺得搞明白元類的人其實也只是自圓其說、點到爲止,從對元類的控制上來看就破綻百出、邏輯混亂,今天我就來帶你們來深度瞭解python元類的前因後果。編程
筆者深刻淺出的背後是對技術一日復一日的執念,但願能夠你們能夠尊重原創,爲你們能所以文而解開對元類全部的疑惑而感到開心!!!函數
class Foo: # Foo=元類() pass
cmd = """ x=1 print('exec函數運行了') def func(self): pass """ class_dic = {} # 執行cmd中的代碼,而後把產生的名字丟入class_dic字典中 exec(cmd, {}, class_dic)
exec函數運行了
print(class_dic)
{'x': 1, 'func': <function func at 0x10a0bc048>}
若是說類也是對象,那麼用class關鍵字的去建立類的過程也是一個實例化的過程,該實例化的目的是爲了獲得一個類,調用的是元類學習
用class關鍵字建立一個類,用的默認的元類type,所以之前說不要用type做爲類別判斷spa
class People: # People=type(...) country = 'China' def __init__(self, name, age): self.name = name self.age = age def eat(self): print('%s is eating' % self.name)
print(type(People))
<class 'type'>
建立類的3個要素:類名,基類,類的名稱空間code
People = type(類名,基類,類的名稱空間)對象
class_name = 'People' # 類名 class_bases = (object, ) # 基類 # 類的名稱空間 class_dic = {} class_body = """ country='China' def __init__(self,name,age): self.name=name self.age=age def eat(self): print('%s is eating' %self.name) """ exec( class_body, {}, class_dic, )
print(class_name)
People
print(class_bases)
(<class 'object'>,)
print(class_dic) # 類的名稱空間
{'country': 'China', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>}
People1 = type(class_name, class_bases, class_dic) print(People1)
<class '__main__.People'>
obj1 = People1(1, 2) obj1.eat()
1 is eating
print(People)
<class '__main__.People'>
obj = People1(1, 2) obj.eat()
1 is eating
class Mymeta(type): # 只有繼承了type類才能稱之爲一個元類,不然就是一個普通的自定義類 def __init__(self, class_name, class_bases, class_dic): print('self:', self) # 如今是People print('class_name:', class_name) print('class_bases:', class_bases) print('class_dic:', class_dic) super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父類type的功能
分析用class自定義類的運行原理(而非元類的的運行原理):blog
拿到一個字符串格式的類名class_name='People'繼承
拿到一個類的基類們class_bases=(obejct,)遞歸
執行類體代碼,拿到一個類的名稱空間class_dic={...}
調用People=type(class_name,class_bases,class_dic)
class People(object, metaclass=Mymeta): # People=Mymeta(類名,基類們,類的名稱空間) country = 'China' def __init__(self, name, age): self.name = name self.age = age def eat(self): print('%s is eating' % self.name)
self: <class '__main__.People'> class_name: People class_bases: (<class 'object'>,) class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>}
自定義元類控制類的產生過程,類的產生過程其實就是元類的調用過程
咱們能夠控制類必須有文檔,可使用以下的方式實現
class Mymeta(type): # 只有繼承了type類才能稱之爲一個元類,不然就是一個普通的自定義類 def __init__(self, class_name, class_bases, class_dic): if class_dic.get('__doc__') is None or len( class_dic.get('__doc__').strip()) == 0: raise TypeError('類中必須有文檔註釋,而且文檔註釋不能爲空') if not class_name.istitle(): raise TypeError('類名首字母必須大寫') super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父類的功能
try: class People(object, metaclass=Mymeta ): #People = Mymeta('People',(object,),{....}) # """這是People類""" country = 'China' def __init__(self, name, age): self.name = name self.age = age def eat(self): print('%s is eating' % self.name) except Exception as e: print(e)
類中必須有文檔註釋,而且文檔註釋不能爲空
class Foo: def __call__(self, *args, **kwargs): print(args) print(kwargs) print('__call__實現了,實例化對象能夠加括號調用了') obj = Foo() obj('nick', age=18)
('nick',) {'age': 18} __call__實現了,實例化對象能夠加括號調用了
咱們以前說類實例化第一個調用的是__init__,但__init__其實不是實例化一個類的時候第一個被調用 的方法。當使用 Persion(name, age) 這樣的表達式來實例化一個類時,最早被調用的方法 實際上是 __new__ 方法。
__new__方法接受的參數雖然也是和__init__同樣,但__init__是在類實例建立以後調用,而 __new__方法正是建立這個類實例的方法。
注意:new() 函數只能用於從object繼承的新式類。
class A: pass class B(A): def __new__(cls): print("__new__方法被執行") return cls.__new__(cls) def __init__(self): print("__init__方法被執行") b = B()
class Mymeta(type): def __call__(self, *args, **kwargs): print(self) # self是People print(args) # args = ('nick',) print(kwargs) # kwargs = {'age':18} # return 123 # 1. 先造出一個People的空對象,申請內存空間 # __new__方法接受的參數雖然也是和__init__同樣,但__init__是在類實例建立以後調用,而 __new__方法正是建立這個類實例的方法。 obj = self.__new__(self) # 雖然和下面一樣是People,可是People沒有,找到的__new__是父類的 # 2. 爲該對空對象初始化獨有的屬性 self.__init__(obj, *args, **kwargs) # 3. 返回一個初始化好的對象 return obj
class People(object, metaclass=Mymeta): country = 'China' def __init__(self, name, age): self.name = name self.age = age def eat(self): print('%s is eating' % self.name) # 在調用Mymeta的__call__的時候,首先會找本身(以下函數)的,本身的沒有才會找父類的 # def __new__(cls, *args, **kwargs): # # print(cls) # cls是People # # cls.__new__(cls) # 錯誤,無限死循環,本身找本身的,會無限遞歸 # obj = super(People, cls).__new__(cls) # 使用父類的,則是去父類中找__new__ # return obj
類的調用,即類實例化就是元類的調用過程,能夠經過元類Mymeta的__call__方法控制
分析:調用Pepole的目的
先造出一個People的空對象
爲該對空對象初始化獨有的屬性
返回一個初始化好的對象
obj = People('nick', age=18)
<class '__main__.People'> ('nick',) {'age': 18}
print(obj.__dict__)
{'name': 'nick', 'age': 18}
結合python繼承的實現原理+元類從新看屬性的查找應該是什麼樣子呢???
在學習完元類後,其實咱們用class自定義的類也全都是對象(包括object類自己也是元類type的 一個實例,能夠用type(object)查看),咱們學習過繼承的實現原理,若是把類當成對象去看,將下述繼承應該說成是:對象OldboyTeacher繼承對象Foo,對象Foo繼承對象Bar,對象Bar繼承對象object
class Mymeta(type): # 只有繼承了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 self.age = age 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
111
print(OldboyTeacher.n)
111
查找順序:
先對象層:OldoyTeacher->Foo->Bar->object
而後元類層:Mymeta->type
依據上述總結,咱們來分析下元類Mymeta中__call__裏的self.__new__的查找
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('nick', 18) # 觸發OldboyTeacher的類中的__call__方法的執行,進而執行self.__new__開始查找
總結,Mymeta下的__call__裏的self.__new__在OldboyTeacher、Foo、Bar裏都沒有找到__new__的狀況下,會去找object裏的__new__,而object下默認就有一個__new__,因此即使是以前的類均未實現__new__,也必定會在object中找到一個,根本不會、也根本不必再去找元類Mymeta->type中查找__new__
需求:使用元類修改屬性爲隱藏屬性
class Mymeta(type): def __init__(self, class_name, class_bases, class_dic): # 加上邏輯,控制類Foo的建立 super(Mymeta, self).__init__(class_name, class_bases, class_dic) def __call__(self, *args, **kwargs): # 加上邏輯,控制Foo的調用過程,即Foo對象的產生過程 obj = self.__new__(self) self.__init__(obj, *args, **kwargs) # 修改屬性爲隱藏屬性 obj.__dict__ = { '_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items() } return obj
class Foo(object, metaclass=Mymeta): # Foo = Mymeta(...) def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex obj = Foo('nick', 18, 'male')
print(obj.__dict__)
{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}