小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

進(jìn)階 | Python 面向切面編程與元類

 達(dá)坂城大豆 2017-12-21

作者:再見紫羅蘭

來源:http://www.cnblogs.com/linxiyue/p/8030604.html

在 Python中,實(shí)例對(duì)象是由類生成的,而類本身也是可以被傳遞和自省的對(duì)象。那么類對(duì)象是用什么創(chuàng)建和生成的呢?答案是元類,元類就是一種知道如何創(chuàng)建和管理類的對(duì)象。

讓我們回顧一個(gè)內(nèi)置函數(shù)type(),type不僅可以返回對(duì)象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數(shù)來創(chuàng)建一個(gè)新類對(duì)象:

  1. >>> Foo = type('Foo',(object,),{'foo':lambda self:'foo'})

  2. >>> Foo

  3. >>> type(Foo)

實(shí)際上,新型類的默認(rèn)元類就是type,類可以用metaclass類變量顯示的指定元類,上述代碼功能與下述相同:

  1. class Foo():

  2.    __metaclass__ = type

  3.    def foo(self):

  4.        return 'foo'

如果沒有顯式的指定元類,class語句會(huì)檢查基類元組中的第一個(gè)基類的元類,比如新型類都是繼承object類的,所以新型類與object類的元類相同,為type,繼承object而不顯式的指定元類:

  1. class Foo(object):

  2.    def foo(self):

  3.        return 'foo'

如果沒有指定基類,class語句會(huì)檢查全局變量metaclass,如果沒有找到metaclass值,Python會(huì)使用默認(rèn)的元類。

在python 2中,默認(rèn)的元類是types.ClassType,就是所謂的舊樣式類。python2.2以后已不提倡使用,比如不指定元類并且不繼承object基類:

  1. class Foo():

  2.    def foo(self):

  3.        return 'foo'

  4. >>> import types

  5. >>> isinstance(Foo, types.ClassType)

  6. True

python 3以后,默認(rèn)的元類皆為type了,顯式定義元類的時(shí)候需要在基類元組中提供metaclass關(guān)鍵字,class Foo(metaclass=type)如此定義。

使用元類的時(shí)候,一般會(huì)自定義一個(gè)繼承自type的子類,并重新實(shí)現(xiàn)init()與new()方法:

  1. class ExampleType(type):

  2.    def __new__(cls, name, bases, dct):

  3.        print 'create class %s'%name

  4.        return type.__new__(cls, name, bases, dct)

  5.    def __init__(cls, name, bases, dct):

  6.        print 'Init class %s'%name

  7.        type.__init__(cls, name, bases, dct)

  8. class Foo(object):

  9.    __metaclass__ = ExampleType

  10. >>>

  11. create class Foo

  12. Init class Foo

  13. >>> Foo

可見,使用class語句定義類后,元類就使用傳遞給元類的類名稱、基類元組和類方法字典創(chuàng)建類。

因?yàn)樵悇?chuàng)建的實(shí)例是類對(duì)象,所以init方法的第一個(gè)參數(shù)按慣例寫為cls,其實(shí)與self功能相同。

面向切面編程

在運(yùn)行時(shí),動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程稱為面向切面的編程(AOP)。

簡(jiǎn)單地說,如果不同的類要實(shí)現(xiàn)相同的功能,可以將其中相同的代碼提取到一個(gè)切片中,等到需要時(shí)再切入到對(duì)象中去。這些相同的代碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點(diǎn)。

比如,要為每個(gè)類方法記錄日志,在python中一個(gè)可行的方法是使用裝飾器:

  1. def trace(func):

  2.    def callfunc(self, *args, **kwargs):

  3.        debug_log = open('debug_log.txt', 'a')

  4.        debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))

  5.        result = func(self, *args, **kwargs)

  6.        debug_log.write('%s returned %sn'%(func.__name__, result))

  7.        debug_log.close()

  8.        return result

  9.    return callfunc

  10. def logcls(cls):

  11.    for k, v in cls.__dict__.items():

  12.        if k.startswith('__'):

  13.            continue

  14.        if not callable(v):

  15.            continue

  16.        setattr(cls, k, trace(v))

  17.    return cls

  18. @logcls

  19. class Foo(object):

  20.    num = 0

  21.    def spam(self):

  22.        Foo.num += 1

  23.        return Foo.num

另外一個(gè)可行的方法就是使用元類了:

  1. def trace(func):

  2.    def callfunc(self, *args, **kwargs):

  3.        debug_log = open('debug_log.txt', 'a')

  4.        debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))

  5.        result = func(self, *args, **kwargs)

  6.        debug_log.write('%s returned %sn'%(func.__name__, result))

  7.        debug_log.close()

  8.        return result

  9.    return callfunc

  10. class LogMeta(type):

  11.    def __new__(cls, name, bases, dct):

  12.        for k, v in dct.items():

  13.            if k.startswith('__'):

  14.                continue

  15.            if not callable(v):

  16.                continue

  17.            dct[k] = trace(v)

  18.        return type.__new__(cls, name, bases, dct)

  19. class Foo(object):

  20.    __metaclass__ = LogMeta

  21.    num = 0

  22.    def spam(self):

  23.        Foo.num += 1

  24.        return Foo.num

元類的一個(gè)主要用途就是檢查收集或者更改類定義的內(nèi)容,包括類屬性、類方法、描述符等等。

元類與基類

元類中除了可以定義initnew方法外,還可以定義其它的屬性和方法:

  1. class ExaMeta(type):

  2.    name = 'ExaMeta'

  3.    def get_cls_name(cls):

  4.        print cls.__name__

  5. class Foo(object):

  6.    __metaclass__ = ExaMeta

那么,類可不可以訪問元類定義的方法和屬性呢?

  1. >>> Foo.get_cls_name()

  2. Foo

  3. >>> Foo.name

  4. 'ExaMeta'

這很好理解,類Foo是元類的一個(gè)實(shí)例,在實(shí)例的dict中查找不到要查詢的屬性時(shí),就會(huì)到實(shí)例所屬的類字典中去查找,而元類正是定義類Foo的類。

可以再嘗試下使用類Foo的實(shí)例去訪問元類的屬性或者方法:

  1. >>> Foo().get_cls_name()

  2. AttributeError: 'Foo' object has no attribute 'get_cls_name'

  3. >>> Foo().name

  4. AttributeError: 'Foo' object has no attribute 'name'

顯然不能訪問。

查找一個(gè)不與實(shí)例關(guān)聯(lián)的屬性時(shí),即先在實(shí)例的類中查找,然后再在從所有的基類中查找,查找的順序可以用mro屬性查看:

  1. >>> Foo.__mro__

  2. (, )

元類并不在其中,畢竟,類與元類不是繼承關(guān)系,而是實(shí)例與類的創(chuàng)造關(guān)系。

元類屬性的可用性是不會(huì)傳遞的,也就是說,元類的屬性是對(duì)它的類實(shí)例是可用的,但是對(duì)它的類實(shí)例的實(shí)例是不可用的,這正是元類與基類的主要不同。

有時(shí)候,一個(gè)類會(huì)同時(shí)有元類和基類:

  1. class M(type):

  2.    name = 'M'

  3. class B(object):

  4.    name = 'B'

  5. class A(B):

  6.    __metaclass__ = M

屬性訪問是這樣的:

  1. >>> A.name

  2. 'B'

  3. >>> A().name

  4. 'B'

可見類會(huì)先到繼承的基類中去查找屬性。

元類沖突

假如有兩個(gè)不同元類的類,要生成一個(gè)繼承這兩個(gè)類的子類,會(huì)產(chǎn)生什么情況呢?

  1. class MA(type):

  2.    pass

  3. class A(object):

  4.    __metaclass__ = MA

  5. class MB(type):

  6.    pass

  7. class B(object):

  8.    __metaclass__ = MB

  9. class C(A, B):

  10.    pass

結(jié)果會(huì)報(bào)錯(cuò),提示元類沖突:

  1. TypeError: Error when calling the metaclass bases

  2.    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我們需要手動(dòng)構(gòu)造新子類的元類,讓新子類的元類繼承自A和B的元類:

  1. class MC(MA, MB):

  2.    pass

  3. class C(A, B):

  4.    __metaclass__ = MC


題圖:pexels,CC0 授權(quán)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多