類元編程

 

類元編程: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')
evaltime.py
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')
evalsupport.py

  場景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')
evaltime_meta.py

場景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__中的超類元組,解釋器會調用這個方法。

相關文章
相關標籤/搜索