類元編程:python
在運行時建立或定製類的技藝。在Python中,類是一等對象,所以任什麼時候候均可以使用函數新建類。而無需使用class關鍵字。數據庫
類裝飾器也是函數,不過能審查、修改,甚至把被裝飾的類替換成其餘類。編程
類工廠函數:緩存
collections.nametuple。咱們把一個類名和幾個屬性名傳給這個函數,它會建立一個tuple的子類,其中的元素經過名稱獲取,還爲調式提供了友好的字符串表示形式(__repr__)app
自定義一個類工廠函數:ide
def record_factory(cls_name, field_name): try: field_name = field_name.replace(',',' ').split() except AttributeError: pass field_name = tuple(field_name) def __init__(self,*args,**kwargs): attr = dict(zip(self.__slots__, args)) attr.update(kwargs) for key,value in attr.items(): setattr(self, key, value) def __iter__(self,): for name in self.__slots__: yield getattr(self,name) def __repr__(self,): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__,self)) temp = '{}({})'.format(self.__class__.__name__,values) return temp cls_attrs = dict(__slots__ = field_name, __init__ = __init__, __iter__ = __iter__, __repr__ = __repr__) return type(cls_name, (object,), cls_attrs) Dog = record_factory('Dog','name age owner') rex = Dog('Rex' , 39 , 'DDD') print(rex.name)
(1)咱們把type視做函數,由於咱們像函數那樣使用它,調用type(my_object)獲取對象所屬的類,做用與my_object.__class__相同;然而,type是一個類。當成類使用時,傳入三個參數能夠新建一個類。函數
MyClass = type(‘MyClass’, (MySuperClass, MyMixin), {'x':42, 'x2':lambda self:self.x *2})spa
type的三的參數分別是name、bases和dict,最後一個參數是一個映射,指定新類的屬性名和值。等效於code
class MyClass(MySuperClass, MyMixin): x = 42 def x2(self): return self.x * 2
特別的,type的實例是類。orm
__slots__屬性的主要特點是節省內存,能處理數百萬個實例,不過也有一些缺點。
把三個參數傳給type是動態建立類的經常使用方式。
collections.nametuple函數有另外一種方式:先聲明一個_class_template變量,其值是字符串形式的源碼模板,而後再namedtuple函數中調用_class_templete.format(...)方法,填充模板裏的空白,
最後,使用內置的exec函數計算獲得的源碼字符串。
▲ record_factory函數建立的類,其實例有個侷限:不能序列化,即不能使用pickle模塊裏的dump/load函數處理。
定製描述符的類裝飾器:
原由:咱們不能使用描述性的存儲屬性名稱,由於實例化描述符時,沒法得知託管屬性的名稱。(即綁定到描述符上的類屬性)
但是,一旦組建好整個類,並且把描述符綁定到類屬性上以後,咱們就能夠審查類,併爲描述符設置合理的存儲屬性名稱。
但是,一旦LineItem類構建好了,描述符與託管屬性之間的綁定就不會變了。所以,咱們要在建立類時設置存儲屬性的名稱。
使用類裝飾器或元類能夠作到這一點。
def entity(cls): for key, attr in cls.__dict__.items(): if isinstance(attr,Validated): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) return cls @entity class LineItem: description = NonBlank() weight = Quantity() price = Quantity() def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price
(1)類裝飾器能以較簡單的方式作到之前須要使用元類去作的事情--建立類時定製類。
(2)類裝飾器有個重大缺點:只對直接依附的類有效。
(3)被裝飾的類的子類可能繼承也可能不繼承裝飾器所作的改動。
導入時和運行時比較:
導入時,Python解釋器從上到下一次性解析完.py模塊的源碼,而後生成用於執行的字節碼。若是句法有錯誤,就在此時報告。
若是本地__pycache__文件夾中有最新的.pyc文件,解釋器會跳過上述步驟,由於已經有運行所需的字節碼了。
編譯確定是導入時的活動,不過那個時期還會作其餘事,由於Python中的語句幾乎都是可執行的,也就是說語句可能會運行用戶代碼,修改用戶程序的狀態。
尤爲是import語句。它不僅是聲明,在進程中首次導入模塊時,還會運行所導入模塊中的所有頂層代碼。
之後導入相同的模塊則使用緩存,只作名稱綁定。那些頂層代碼能夠作任何事情,包括一般在「運行時」作的事,(鏈接數據庫)
導入時和運行時界線是模糊的,import語句能夠觸發任何「運行時」的行爲。
解釋器會編譯函數的定義體(首次導入模塊時),把函數對象綁定到對應的全局名稱上,可是顯然解釋器不會執行函數的定義體。
一般這意味着解釋器在導入時定義頂層函數,可是僅當在運行時調用函數時纔會執行函數的定義體。
對類來講,在導入時,解釋器會執行每一個類的定義體,甚至會執行嵌套類的定義體。執行類定義體的結果是,定義了類的屬性和方法,並構建了類對象。
類的定義體屬於「頂層代碼」,由於他在導入時運行。
計算時間的練習:
場景1:>>> import evaltime
場景2:>>> python3 evaltime.py
from evalsupport import deco_alpha print('<[1]> evaltime module start') class ClassOne(): print('<[2]> ClassOne body') def __init__(self): print('<[3]> ClassOne.__init__') def __del__(self): print('<[4]> ClassOne.__del__') def method_x(self): print('<[5]> ClassOne.method_x') class ClassTwo(object): print('<[6]> ClassTwo body') @deco_alpha class ClassThree(): print('<[7]> ClassThree body') def method_y(self): print('<[8]> ClassThree.method_y') class ClassFour(ClassThree): print('<[9]> ClassFour body') if __name__ == '__main__': print('<[11]> ClassOne tests', 30 * '.') one = ClassOne() one.method_x() print('<[12]> ClassThree tests', 30 * '.') three = ClassThree() three.method_y() print('<[13]> ClassFour tests', 30 * '.') four = ClassFour() four.method_y() print('<[14]> evaltime module end')
print('<[100]> evalsupport module start') def deco_alpha(cls): print('<[200]> deco_alpha') def inner_1(self): print('<[300]> deco_alpha:inner_1') cls.method_y = inner_1 return cls class MetaAleph(type): print('<[400]> MetaAleph body') def __init__(cls, name, bases, dic): print('<[500]> MetaAleph.__init__') def inner_2(self): print('<[600]> MetaAleph.__init__:inner_2') cls.method_z = inner_2 print('<[700]> evalsupport module end')
場景1運行結果:
>>> import evaltime <[100]> evalsupport module start <[400]> MetaAleph body # 說明類的 body 在導入時就執行 <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body # 類的 body 中有其餘類, 也會被執行 <[7]> ClassThree body # 先執行類的body, 而後將類做爲類裝飾器的參數 <[200]> deco_alpha <[9]> ClassFour body # 類繼承並不會繼承類裝飾器 <[14]> evaltime module end
場景2運行結果:
python evaltime.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body <[11]> ClassOne tests .............................. <[3]> ClassOne.__init__ <[5]> ClassOne.method_x <[12]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 # 裝飾器修改了類屬性 <[13]> ClassFour tests .............................. <[10]> deco_alpha:inner_1 # ClassFour 繼承的是經由裝飾器修改後的ClassThree <[14]> evaltime module end <[4]> ClassOne.__del__ # 退出時垃圾回收
總結:
(1)解釋器會執行所導入模塊及其依賴中的每一個類定義體。
(2)解釋器先計算類的定義體,而後調用依附在類上的裝飾器函數,先構建類對象,裝飾器纔有類對象可處理。
(3)類裝飾器對子類沒有影響,除非子類使用了super()語句。
元類基礎知識:
元類是製造類的工廠,不過不是函數,而是類。
根據Python對象模型,類是對象,所以類確定是另外某個類的實例。
默認狀況下,Python中的類是type類的實例。也就是說,type是大多數內置的類和用戶定義的類的元類;
爲了不無限回溯,type是其自身的實例。(object是type的實例,而type是object的子類)
除了type,標準庫中還有一些別的元類,如:ABCMeta和Enum。
元類從type類繼承了構建類的能力。(全部類都是type的實例,可是元類仍是type的子類。)
元類能夠經過實現__init__方法定製實例。元類的__init__方法能夠作到類裝飾器能作到的任何事情,可是做用更大。
元類計算時間的練習:
場景3:>>> import evaltime_meta
場景4:>>> python3 evaltime_meta.py
from evalsupport import deco_alpha from evalsupport import MetaAleph print('<[1]> evaltime_meta module start') @deco_alpha class ClassThree(): print('<[2]> ClassThree body') def method_y(self): print('<[3]> ClassThree.method_y') class ClassFour(ClassThree): print('<[4]> ClassFour body') def method_y(self): print('<[5]> ClassFour.method_y') class ClassFive(metaclass=MetaAleph): print('<[6]> ClassFive body') def __init__(self): print('<[7]> ClassFive.__init__') def method_z(self): print('<[8]> ClassFive.method_y') class ClassSix(ClassFive): print('<[9]> ClassSix body') def method_z(self): print('<[10]> ClassSix.method_y') if __name__ == '__main__': print('<[11]> ClassThree tests', 30 * '.') three = ClassThree() three.method_y() print('<[12]> ClassFour tests', 30 * '.') four = ClassFour() four.method_y() print('<[13]> ClassFive tests', 30 * '.') five = ClassFive() five.method_z() print('<[14]> ClassSix tests', 30 * '.') six = ClassSix() six.method_z() print('<[15]> evaltime_meta module end')
場景3運行結果:
>>> import evaltime_meta <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime_meta module start <[2]> ClassThree body <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ # 先 ClassFour 定義, 而後交給其元類對他加工 <[9]> ClassSix body <[500]> MetaAleph.__init__ # 先 ClassFour 定義, 而後交給其元類對他加工, 因爲繼承自 ClassFive, 其元類同上(注意區分基類與元類) <[15]> evaltime_meta module end
場景4運行結果:
python3 evaltime_meta.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime_meta module start <[2]> ClassThree body <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ <[9]> ClassSix body <[500]> MetaAleph.__init__ <[11]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 <[12]> ClassFour tests .............................. <[5]> ClassFour.method_y <[13]> ClassFive tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[14]> ClassSix tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[15]> evaltime_meta module end
總結:
(1)建立ClassFive時調用了MetaAleph.__init__方法。
(2)建立ClassFive的子類ClassSix時也調用了MetaAleph.__init__方法。
(3)__init__方法,四個參數:self(或者寫成 cls):要初始化的類對象,(如ClassFive),name、bases、dic與構建類時傳給type的參數同樣。
(4)先執行類的定義體,再去執行元類的__init__方法。
注意:
ClassSix類沒有直接引用MetaAleph類,可是卻受到了影響,由於它是ClassFive的子類,進而也是MetaAleph類的實例,因此由MetaAleph.__init__方法初始化。
class FooMeta(type): print('FooMeta__class__') def __init__(cls,name,bases,dic): print('FooMeta.__init__') class Foo(metaclass=FooMeta): print('Foo__class__') def __init__(self): print('Foo.__init__') FooMeta__class__ Foo__class__ FooMeta.__init__
定製描述符的元類:
class EntityMeta(type): def __init__(cls, name, bases, attr_dict): super().__init__(name, bases, attr_dict) for key, attr in attr_dict.items(): if isinstance(attr, Validated): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) class Entity(metaclass=EntityMeta): """""" class LineItem(Entity): description = NonBlank() weight = Quantity() price = Quantity() def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price
元類的特殊方法__prepare__:
元類或者類裝飾器得到映射時,屬性在類定義體中的順序已經丟失。
特殊方法__prepare__只在元類中有用,並且必須聲明爲類方法(用@classmethod 裝飾器定義)。
解釋器調用元類的__new__方法前,會先調用__prepare__方法,使用類定義體中的屬性建立映射。
__prepare__方法的第一個參數是元類,隨後兩個參數分別是要構建的類的名稱和基類組成的元組。返回值必須是映射。
元類構建新類時,__prepare__方法返回的映射會傳給__new__方法的最後一個參數,而後再傳給__init__方法。
class EntityMeta(type): @classmethod def __prepare__(metacls, name, bases): return collections.OrderedDict() def __init__(self, cls, bases, attr_dict): super().__init__(cls, bases, attr_dict) cls._field_names = [] for key, attr in attr_dict.items(): if isinstance(attr, Validated): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) cls._field_names.append(key) class Entity(metaclass=EntityMeta): """帶有驗證字段的業務實體""" @classmethod def field_names(cls): for key in cls._field_names: yield key
類做爲對象:
__mor__、__class__、__name__
cls.__bases__;由類的基類組成的元組
cls.__qualname__;類或函數的限定名稱,
cls.__subclasses__();返回列表,包含類的直接子類,是內存裏現存的子類。
cls.mro();構建類時,若是須要獲取存儲在類屬性__mro__中的超類元組,解釋器會調用這個方法。